@statezero/core 0.1.51 → 0.1.53

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.
@@ -11,13 +11,7 @@ export class FileObject {
11
11
  lastModified: number | null;
12
12
  uploaded: boolean;
13
13
  uploading: boolean;
14
- uploadResult: {
15
- file_path: any;
16
- file_name: any;
17
- file_url: any;
18
- size: any;
19
- mime_type: any;
20
- } | null;
14
+ uploadResult: any;
21
15
  uploadError: any;
22
16
  fileData: any;
23
17
  uploadType: string | null;
@@ -31,7 +25,6 @@ export class FileObject {
31
25
  get status(): "failed" | "uploading" | "uploaded" | "pending";
32
26
  get filePath(): any;
33
27
  get fileUrl(): any;
34
- serialize(): any;
35
28
  _initializeAndStartUpload(file: any, options: any): any;
36
29
  /**
37
30
  * Fast upload using S3 presigned URLs with multipart support
@@ -40,23 +33,11 @@ export class FileObject {
40
33
  /**
41
34
  * Handle single file upload
42
35
  */
43
- _singleUpload(file: any, uploadData: any, options: any): Promise<{
44
- file_path: any;
45
- file_name: any;
46
- file_url: any;
47
- size: any;
48
- mime_type: any;
49
- } | null>;
36
+ _singleUpload(file: any, uploadData: any, options: any): Promise<any>;
50
37
  /**
51
38
  * Handle multipart upload with concurrency using p-queue
52
39
  */
53
- _multipartUpload(file: any, uploadData: any, options: any): Promise<{
54
- file_path: any;
55
- file_name: any;
56
- file_url: any;
57
- size: any;
58
- mime_type: any;
59
- } | null>;
40
+ _multipartUpload(file: any, uploadData: any, options: any): Promise<any>;
60
41
  /**
61
42
  * Create file chunks for multipart upload
62
43
  */
@@ -64,13 +45,7 @@ export class FileObject {
64
45
  /**
65
46
  * Complete the upload (both single and multipart)
66
47
  */
67
- _completeUpload(filePath: any, originalName: any, uploadId?: null, parts?: null): Promise<{
68
- file_path: any;
69
- file_name: any;
70
- file_url: any;
71
- size: any;
72
- mime_type: any;
73
- } | null>;
48
+ _completeUpload(filePath: any, originalName: any, uploadId?: null, parts?: null): Promise<any>;
74
49
  /**
75
50
  * Direct upload to Django backend (original method)
76
51
  */
@@ -85,10 +60,18 @@ export class FileObject {
85
60
  getBlob(): Blob;
86
61
  waitForUpload(): Promise<any>;
87
62
  toJSON(): {
63
+ name: any;
64
+ size: any;
65
+ type: any;
66
+ status: string;
67
+ uploaded: boolean;
88
68
  filePath: any;
89
- fileName: any;
90
69
  fileUrl: any;
91
- size: any;
92
- mimeType: any;
70
+ uploadResult: any;
71
+ uploadError: string | null;
72
+ uploadType: string | null;
73
+ uploadId: any;
74
+ totalChunks: number;
75
+ completedChunks: number;
93
76
  };
94
77
  }
@@ -10,24 +10,17 @@ export class FileObject {
10
10
  // Handle stored file data (from API)
11
11
  if (file &&
12
12
  typeof file === "object" &&
13
- (file.file_path || file.filePath) && // Handle both formats
13
+ file.file_path &&
14
14
  !(file instanceof File)) {
15
- // Handle both snake_case (from backend) and camelCase (from toJSON)
16
- this.name = file.file_name || file.fileName;
15
+ // This is stored file data from the backend
16
+ this.name = file.file_name;
17
17
  this.size = file.size;
18
- this.type = file.mime_type || file.mimeType;
18
+ this.type = file.mime_type; // Now coming from backend
19
19
  this.lastModified = null;
20
20
  // Mark as already uploaded
21
21
  this.uploaded = true;
22
22
  this.uploading = false;
23
- // Store the entire response, normalizing format
24
- this.uploadResult = {
25
- file_path: file.file_path || file.filePath,
26
- file_name: file.file_name || file.fileName,
27
- file_url: file.file_url || file.fileUrl,
28
- size: file.size,
29
- mime_type: file.mime_type || file.mimeType,
30
- };
23
+ this.uploadResult = file; // Store the entire response
31
24
  this.uploadError = null;
32
25
  this.fileData = null;
33
26
  // No upload properties needed
@@ -66,13 +59,6 @@ export class FileObject {
66
59
  `Provided: ${this.chunkSize / (1024 * 1024)}MB`);
67
60
  }
68
61
  this.maxConcurrency = options.maxConcurrency || 3;
69
- // This is to make the fileObject serializable by IDB in the cache
70
- Object.defineProperty(this, "uploadPromise", {
71
- value: Promise.resolve(this.uploadResult),
72
- writable: true,
73
- enumerable: false,
74
- configurable: true,
75
- });
76
62
  this.uploadPromise = this._initializeAndStartUpload(file, options);
77
63
  }
78
64
  get isStoredFile() {
@@ -96,24 +82,6 @@ export class FileObject {
96
82
  }
97
83
  return configInstance.buildFileUrl(this.uploadResult.file_url, this.constructor.configKey);
98
84
  }
99
- serialize() {
100
- const status = this.status;
101
- if (status === "uploaded" && this.filePath) {
102
- return this.filePath;
103
- }
104
- else if (status === "failed") {
105
- throw new Error(`Cannot use FileObject in query - upload failed: ${this.uploadError}`);
106
- }
107
- else if (status === "uploading") {
108
- throw new Error(`Cannot use FileObject in query - file is still uploading. Wait for upload to complete before executing the query.`);
109
- }
110
- else if (status === "pending") {
111
- throw new Error(`Cannot use FileObject in query - file upload has not started yet.`);
112
- }
113
- else {
114
- throw new Error(`Cannot use FileObject in query - unexpected status: ${status}`);
115
- }
116
- }
117
85
  async _initializeAndStartUpload(file, options) {
118
86
  const config = configInstance.getConfig();
119
87
  const backend = config.backendConfigs?.[this.constructor.configKey];
@@ -384,11 +352,19 @@ export class FileObject {
384
352
  }
385
353
  toJSON() {
386
354
  return {
355
+ name: this.name,
356
+ size: this.size,
357
+ type: this.type,
358
+ status: this.status,
359
+ uploaded: this.uploaded,
387
360
  filePath: this.filePath,
388
- fileName: this.name,
389
361
  fileUrl: this.fileUrl,
390
- size: this.size,
391
- mimeType: this.type,
362
+ uploadResult: this.uploadResult,
363
+ uploadError: this.uploadError ? String(this.uploadError) : null,
364
+ uploadType: this.uploadType,
365
+ uploadId: this.uploadId,
366
+ totalChunks: this.totalChunks,
367
+ completedChunks: this.completedChunks,
392
368
  };
393
369
  }
394
370
  }
@@ -37,6 +37,51 @@ export function processIncludedEntities(modelStoreRegistry, included, ModelClass
37
37
  throw new Error(`Failed to process included entities: ${error.message}`);
38
38
  }
39
39
  }
40
+ /**
41
+ * Recursively processes an object to replace FileObject instances with their file paths.
42
+ * Throws an error if any FileObject is not yet uploaded.
43
+ *
44
+ * @param {any} obj - The object to process
45
+ * @returns {any} The processed object with FileObjects replaced by paths
46
+ */
47
+ function processFileObjects(obj) {
48
+ if (obj === null || obj === undefined) {
49
+ return obj;
50
+ }
51
+ // Handle FileObject instances
52
+ if (obj instanceof FileObject) {
53
+ const status = obj.status;
54
+ if (status === 'uploaded' && obj.filePath) {
55
+ return obj.filePath;
56
+ }
57
+ else if (status === 'error') {
58
+ throw new Error(`Cannot use FileObject in query - upload failed: ${obj.uploadError}`);
59
+ }
60
+ else if (status === 'uploading') {
61
+ throw new Error(`Cannot use FileObject in query - file is still uploading. Wait for upload to complete before executing the query.`);
62
+ }
63
+ else if (status === 'pending') {
64
+ throw new Error(`Cannot use FileObject in query - file upload has not started yet.`);
65
+ }
66
+ else {
67
+ throw new Error(`Cannot use FileObject in query - unexpected status: ${status}`);
68
+ }
69
+ }
70
+ // Handle arrays
71
+ if (Array.isArray(obj)) {
72
+ return obj.map(item => processFileObjects(item));
73
+ }
74
+ // Handle plain objects
75
+ if (typeof obj === 'object' && obj.constructor === Object) {
76
+ const processedObj = {};
77
+ for (const [key, value] of Object.entries(obj)) {
78
+ processedObj[key] = processFileObjects(value);
79
+ }
80
+ return processedObj;
81
+ }
82
+ // Return primitive values as-is
83
+ return obj;
84
+ }
40
85
  /**
41
86
  * Makes an API call to the backend with the given QuerySet.
42
87
  * Automatically handles FileObject replacement with file paths for write operations.
@@ -84,6 +129,15 @@ export async function makeApiCall(querySet, operationType, args = {}, operationI
84
129
  "get_or_create", "update_or_create"
85
130
  ];
86
131
  const isWriteOperation = writeOperations.includes(operationType);
132
+ // Process FileObjects for write operations
133
+ if (isWriteOperation) {
134
+ try {
135
+ payload = processFileObjects(payload);
136
+ }
137
+ catch (error) {
138
+ throw new Error(`Failed to process file uploads: ${error.message}`);
139
+ }
140
+ }
87
141
  const baseUrl = backend.API_URL.replace(/\/+$/, "");
88
142
  const finalUrl = `${baseUrl}/${ModelClass.modelName}/`;
89
143
  const headers = backend.getAuthHeaders ? backend.getAuthHeaders() : {};
@@ -13,7 +13,6 @@ import { FileObject } from './files.js';
13
13
  import { configInstance } from "../../config.js";
14
14
  import { parseStateZeroError, MultipleObjectsReturned, DoesNotExist, } from "./errors.js";
15
15
  import axios from "axios";
16
- import { typeOf } from "mathjs";
17
16
  /**
18
17
  * A constructor for a Model.
19
18
  *
@@ -101,15 +100,16 @@ export class Model {
101
100
  // Let DateParsingHelpers.parseDate throw if it fails
102
101
  return DateParsingHelpers.parseDate(value, field, ModelClass.schema);
103
102
  }
103
+ // File/Image fields need special handling - wrap as FileObject
104
104
  const fileFormats = ["file-path", "image-path"];
105
105
  if (ModelClass.schema &&
106
106
  fileFormats.includes(ModelClass.schema.properties[field]?.format) &&
107
107
  value) {
108
- // If it's already a FileObject, serialize it
108
+ // Check if it's already a FileObject
109
109
  if (value instanceof FileObject) {
110
- return value.toJSON();
110
+ return value;
111
111
  }
112
- // If it's stored file data from API, create FileObject and serialize
112
+ // If it's stored file data from API, wrap it as FileObject
113
113
  if (typeof value === "object" && value.file_path) {
114
114
  // Create anonymous subclass with correct configKey
115
115
  const BackendFileObject = (_a = class extends FileObject {
@@ -117,12 +117,7 @@ export class Model {
117
117
  __setFunctionName(_a, "BackendFileObject"),
118
118
  _a.configKey = ModelClass.configKey,
119
119
  _a);
120
- const fileObj = new BackendFileObject(value);
121
- return fileObj.toJSON(); // Get the computed fileUrl, etc.
122
- }
123
- // If it's just a file path string, return as-is
124
- if (typeof value === "string") {
125
- return value;
120
+ return new BackendFileObject(value);
126
121
  }
127
122
  }
128
123
  // relationship fields need special handling
@@ -215,6 +210,12 @@ export class Model {
215
210
  // Let DateParsingHelpers.serializeDate throw if it fails
216
211
  return DateParsingHelpers.serializeDate(value, field, ModelClass.schema);
217
212
  }
213
+ const fileFormats = ["file-path", "image-path"];
214
+ if (ModelClass.schema &&
215
+ fileFormats.includes(ModelClass.schema.properties[field]?.format) &&
216
+ value) {
217
+ return value.filePath || value.file_path || value;
218
+ }
218
219
  return value;
219
220
  }
220
221
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statezero/core",
3
- "version": "0.1.51",
3
+ "version": "0.1.53",
4
4
  "type": "module",
5
5
  "module": "ESNext",
6
6
  "description": "The type-safe frontend client for StateZero - connect directly to your backend models with zero boilerplate",