hazo_files 1.4.7 → 1.5.2

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.
@@ -0,0 +1,474 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/background_upload/react/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ FileUploadContext: () => FileUploadContext,
34
+ HazoFileUploadProvider: () => HazoFileUploadProvider,
35
+ useFileUpload: () => useFileUpload,
36
+ useFileUploadToasts: () => useFileUploadToasts,
37
+ useJobStatus: () => useJobStatus
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+
41
+ // src/background_upload/react/provider.tsx
42
+ var import_react3 = require("react");
43
+
44
+ // src/background_upload/core/job.ts
45
+ var Job = class {
46
+ constructor(init) {
47
+ this.status = "queued";
48
+ this.progress = null;
49
+ this.error = null;
50
+ this.confirmation_payload = null;
51
+ this.confirmation_resolve = null;
52
+ this.job_id = crypto.randomUUID();
53
+ this.batch_id = init.batch_id;
54
+ this.created_at = Date.now();
55
+ this.updated_at = this.created_at;
56
+ this.pipeline_steps = init.pipeline_steps;
57
+ this.context = {
58
+ files: init.files.map((f) => ({
59
+ file: f,
60
+ file_name: f.name,
61
+ mime_type: f.type
62
+ })),
63
+ group_id: init.group_id,
64
+ group_label: init.group_label,
65
+ extracted_data: {},
66
+ metadata: { ...init.metadata ?? {} }
67
+ };
68
+ }
69
+ get_snapshot() {
70
+ return {
71
+ job_id: this.job_id,
72
+ batch_id: this.batch_id,
73
+ status: this.status,
74
+ group_id: this.context.group_id,
75
+ group_label: this.context.group_label,
76
+ files: [...this.context.files],
77
+ progress: this.progress ? { ...this.progress } : null,
78
+ extracted_data: { ...this.context.extracted_data },
79
+ error: this.error,
80
+ metadata: { ...this.context.metadata },
81
+ confirmation_payload: this.confirmation_payload,
82
+ created_at: this.created_at,
83
+ updated_at: this.updated_at
84
+ };
85
+ }
86
+ set_status(status) {
87
+ this.status = status;
88
+ this.updated_at = Date.now();
89
+ }
90
+ set_progress(current, total) {
91
+ this.progress = { current, total };
92
+ this.updated_at = Date.now();
93
+ }
94
+ set_error(message) {
95
+ this.error = message;
96
+ this.updated_at = Date.now();
97
+ }
98
+ request_confirmation(payload) {
99
+ this.confirmation_payload = payload;
100
+ this.set_status("awaiting_confirmation");
101
+ return new Promise((resolve) => {
102
+ this.confirmation_resolve = resolve;
103
+ });
104
+ }
105
+ resolve_confirmation(result) {
106
+ if (this.confirmation_resolve) {
107
+ this.confirmation_resolve(result);
108
+ this.confirmation_resolve = null;
109
+ this.confirmation_payload = null;
110
+ this.updated_at = Date.now();
111
+ }
112
+ }
113
+ get_context() {
114
+ return this.context;
115
+ }
116
+ get_pipeline_steps() {
117
+ return this.pipeline_steps;
118
+ }
119
+ };
120
+
121
+ // src/background_upload/core/pipeline-executor.ts
122
+ var PipelineExecutor = class {
123
+ async execute(job, emitter) {
124
+ const steps = job.get_pipeline_steps();
125
+ const context = job.get_context();
126
+ const handle = {
127
+ job_id: job.job_id,
128
+ set_status: (status) => {
129
+ const previous_status = job.get_snapshot().status;
130
+ job.set_status(status);
131
+ emitter.emit("job:status_changed", { job: job.get_snapshot(), previous_status });
132
+ },
133
+ set_progress: (current, total) => {
134
+ job.set_progress(current, total);
135
+ emitter.emit("job:progress", { job: job.get_snapshot() });
136
+ },
137
+ request_confirmation: async (payload) => {
138
+ const previous_status = job.get_snapshot().status;
139
+ const result_promise = job.request_confirmation(payload);
140
+ emitter.emit("job:confirmation_needed", { job: job.get_snapshot(), payload });
141
+ emitter.emit("job:status_changed", { job: job.get_snapshot(), previous_status });
142
+ const result = await result_promise;
143
+ emitter.emit("job:confirmation_resolved", { job: job.get_snapshot(), result });
144
+ return result;
145
+ },
146
+ is_cancelled: () => job.get_snapshot().status === "error"
147
+ };
148
+ try {
149
+ for (const step of steps) {
150
+ if (handle.is_cancelled()) break;
151
+ await step.execute(context, handle);
152
+ }
153
+ if (!handle.is_cancelled()) {
154
+ const previous_status = job.get_snapshot().status;
155
+ job.set_status("done");
156
+ emitter.emit("job:status_changed", { job: job.get_snapshot(), previous_status });
157
+ emitter.emit("job:completed", { job: job.get_snapshot() });
158
+ }
159
+ } catch (err) {
160
+ const message = err instanceof Error ? err.message : String(err);
161
+ const previous_status = job.get_snapshot().status;
162
+ job.set_error(message);
163
+ job.set_status("error");
164
+ emitter.emit("job:status_changed", { job: job.get_snapshot(), previous_status });
165
+ emitter.emit("job:error", { job: job.get_snapshot(), error: message });
166
+ }
167
+ }
168
+ };
169
+
170
+ // src/background_upload/core/event-emitter.ts
171
+ var TypedEventEmitter = class {
172
+ constructor() {
173
+ this.listeners = /* @__PURE__ */ new Map();
174
+ }
175
+ on(event, listener) {
176
+ let set = this.listeners.get(event);
177
+ if (!set) {
178
+ set = /* @__PURE__ */ new Set();
179
+ this.listeners.set(event, set);
180
+ }
181
+ set.add(listener);
182
+ return () => this.off(event, listener);
183
+ }
184
+ emit(event, data) {
185
+ const set = this.listeners.get(event);
186
+ if (!set) return;
187
+ for (const listener of set) {
188
+ listener(data);
189
+ }
190
+ }
191
+ off(event, listener) {
192
+ this.listeners.get(event)?.delete(listener);
193
+ }
194
+ removeAllListeners() {
195
+ this.listeners.clear();
196
+ }
197
+ };
198
+
199
+ // src/background_upload/core/upload-manager.ts
200
+ var UploadManager = class {
201
+ constructor(config) {
202
+ this.jobs = /* @__PURE__ */ new Map();
203
+ this.batches = /* @__PURE__ */ new Map();
204
+ this.executor = new PipelineExecutor();
205
+ this.emitter = new TypedEventEmitter();
206
+ this.queue = [];
207
+ this.running = 0;
208
+ this.destroyed = false;
209
+ this.config = {
210
+ max_concurrent: config?.max_concurrent ?? 1,
211
+ default_pipeline_steps: config?.default_pipeline_steps ?? []
212
+ };
213
+ this.emitter.on("job:completed", ({ job }) => this.on_job_settled(job));
214
+ this.emitter.on("job:error", ({ job }) => this.on_job_settled(job));
215
+ }
216
+ on(event, listener) {
217
+ return this.emitter.on(event, listener);
218
+ }
219
+ submit_batch(options) {
220
+ if (this.destroyed) throw new Error("UploadManager is destroyed");
221
+ const batch_id = crypto.randomUUID();
222
+ const job_ids = [];
223
+ const pipeline_steps = options.pipeline_steps ?? this.config.default_pipeline_steps;
224
+ for (const file of options.files) {
225
+ const job = new Job({
226
+ batch_id,
227
+ files: [file],
228
+ group_id: options.group_id,
229
+ group_label: options.group_label,
230
+ pipeline_steps,
231
+ metadata: options.metadata
232
+ });
233
+ this.jobs.set(job.job_id, job);
234
+ job_ids.push(job.job_id);
235
+ this.emitter.emit("job:created", { job: job.get_snapshot() });
236
+ this.queue.push(job.job_id);
237
+ }
238
+ this.batches.set(batch_id, {
239
+ batch_id,
240
+ job_ids,
241
+ total: options.files.length,
242
+ completed: 0,
243
+ failed: 0,
244
+ emitted_complete: false
245
+ });
246
+ this.process_queue();
247
+ return batch_id;
248
+ }
249
+ get_jobs_for_group(group_id) {
250
+ const out = [];
251
+ for (const job of this.jobs.values()) {
252
+ const snap = job.get_snapshot();
253
+ if (snap.group_id === group_id) out.push(snap);
254
+ }
255
+ return out;
256
+ }
257
+ get_job(job_id) {
258
+ return this.jobs.get(job_id)?.get_snapshot() ?? null;
259
+ }
260
+ get_active_jobs() {
261
+ const out = [];
262
+ for (const job of this.jobs.values()) {
263
+ const snap = job.get_snapshot();
264
+ if (snap.status !== "done" && snap.status !== "error") out.push(snap);
265
+ }
266
+ return out;
267
+ }
268
+ resolve_confirmation(job_id, result) {
269
+ const job = this.jobs.get(job_id);
270
+ if (job) job.resolve_confirmation(result);
271
+ }
272
+ destroy() {
273
+ this.destroyed = true;
274
+ this.emitter.removeAllListeners();
275
+ this.jobs.clear();
276
+ this.batches.clear();
277
+ this.queue = [];
278
+ }
279
+ process_queue() {
280
+ while (!this.destroyed && this.queue.length > 0 && this.running < this.config.max_concurrent) {
281
+ const job_id = this.queue.shift();
282
+ const job = this.jobs.get(job_id);
283
+ if (!job) continue;
284
+ this.running++;
285
+ void this.executor.execute(job, this.emitter).finally(() => {
286
+ this.running--;
287
+ this.process_queue();
288
+ });
289
+ }
290
+ }
291
+ on_job_settled(job) {
292
+ const batch = this.batches.get(job.batch_id);
293
+ if (!batch) return;
294
+ let completed = 0, failed = 0;
295
+ for (const jid of batch.job_ids) {
296
+ const s = this.jobs.get(jid)?.get_snapshot().status;
297
+ if (s === "done") completed++;
298
+ else if (s === "error") failed++;
299
+ }
300
+ batch.completed = completed;
301
+ batch.failed = failed;
302
+ this.emitter.emit("batch:progress", { batch: { ...batch } });
303
+ if (!batch.emitted_complete && completed + failed === batch.total) {
304
+ batch.emitted_complete = true;
305
+ this.emitter.emit("batch:completed", { batch: { ...batch } });
306
+ }
307
+ }
308
+ };
309
+
310
+ // src/background_upload/react/context.ts
311
+ var import_react = require("react");
312
+ var FileUploadContext = (0, import_react.createContext)(null);
313
+
314
+ // src/background_upload/react/use-file-upload-toasts.ts
315
+ var import_react2 = require("react");
316
+ var toast_promise = null;
317
+ function get_toast() {
318
+ if (!toast_promise) {
319
+ toast_promise = import("sonner").then((mod) => mod.toast).catch(() => null);
320
+ }
321
+ return toast_promise;
322
+ }
323
+ function useFileUploadToasts(manager) {
324
+ (0, import_react2.useEffect)(() => {
325
+ const unsubs = [];
326
+ unsubs.push(manager.on("job:completed", ({ job }) => {
327
+ void get_toast().then((t) => {
328
+ if (!t) return;
329
+ const label = job.group_label || job.group_id;
330
+ const n = job.files.length;
331
+ t.success(`${n} file${n > 1 ? "s" : ""} processed for ${label}`);
332
+ });
333
+ }));
334
+ unsubs.push(manager.on("job:error", ({ job, error }) => {
335
+ void get_toast().then((t) => {
336
+ if (!t) return;
337
+ const label = job.group_label || job.group_id;
338
+ t.error(`Upload failed for ${label}: ${error}`);
339
+ });
340
+ }));
341
+ unsubs.push(manager.on("job:confirmation_needed", ({ job }) => {
342
+ void get_toast().then((t) => {
343
+ if (!t) return;
344
+ const label = job.group_label || job.group_id;
345
+ t.info(`Action required for ${label}`, {
346
+ description: "Review and confirm data conflicts",
347
+ duration: 1e4
348
+ });
349
+ });
350
+ }));
351
+ unsubs.push(manager.on("batch:completed", ({ batch }) => {
352
+ void get_toast().then((t) => {
353
+ if (!t) return;
354
+ if (batch.failed > 0) {
355
+ t.warning(`Batch complete: ${batch.completed}/${batch.total} succeeded`);
356
+ }
357
+ });
358
+ }));
359
+ return () => unsubs.forEach((u) => u());
360
+ }, [manager]);
361
+ }
362
+
363
+ // src/background_upload/react/toast-bridge.tsx
364
+ function ToastBridge({ manager }) {
365
+ useFileUploadToasts(manager);
366
+ return null;
367
+ }
368
+
369
+ // src/background_upload/react/provider.tsx
370
+ var import_jsx_runtime = require("react/jsx-runtime");
371
+ function HazoFileUploadProvider({
372
+ children,
373
+ config,
374
+ enable_toasts = true
375
+ }) {
376
+ const manager_ref = (0, import_react3.useRef)(null);
377
+ if (!manager_ref.current) {
378
+ manager_ref.current = new UploadManager(config);
379
+ }
380
+ (0, import_react3.useEffect)(() => {
381
+ const m = manager_ref.current;
382
+ return () => {
383
+ m.destroy();
384
+ };
385
+ }, []);
386
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(FileUploadContext.Provider, { value: manager_ref.current, children: [
387
+ enable_toasts && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToastBridge, { manager: manager_ref.current }),
388
+ children
389
+ ] });
390
+ }
391
+
392
+ // src/background_upload/react/use-file-upload.ts
393
+ var import_react4 = require("react");
394
+ var EMPTY_ACTIVE_JOBS = Object.freeze([]);
395
+ function useFileUpload() {
396
+ const manager = (0, import_react4.useContext)(FileUploadContext);
397
+ if (!manager) {
398
+ throw new Error("useFileUpload must be used within HazoFileUploadProvider");
399
+ }
400
+ const snapshot_ref = (0, import_react4.useRef)([]);
401
+ const active_jobs = (0, import_react4.useSyncExternalStore)(
402
+ (callback) => {
403
+ const unsubs = [
404
+ manager.on("job:created", callback),
405
+ manager.on("job:status_changed", callback),
406
+ manager.on("job:progress", callback),
407
+ manager.on("job:completed", callback),
408
+ manager.on("job:error", callback)
409
+ ];
410
+ return () => unsubs.forEach((u) => u());
411
+ },
412
+ () => {
413
+ const next = manager.get_active_jobs();
414
+ const prev = snapshot_ref.current;
415
+ if (prev.length === next.length && next.every((j, i) => j.job_id === prev[i]?.job_id && j.updated_at === prev[i]?.updated_at)) {
416
+ return prev;
417
+ }
418
+ snapshot_ref.current = next;
419
+ return next;
420
+ },
421
+ () => EMPTY_ACTIVE_JOBS
422
+ );
423
+ const submit_batch = (0, import_react4.useCallback)(
424
+ (options) => manager.submit_batch(options),
425
+ [manager]
426
+ );
427
+ const get_job = (0, import_react4.useCallback)((id) => manager.get_job(id), [manager]);
428
+ const get_jobs_for_group = (0, import_react4.useCallback)(
429
+ (id) => manager.get_jobs_for_group(id),
430
+ [manager]
431
+ );
432
+ const resolve_confirmation = (0, import_react4.useCallback)(
433
+ (id, r) => manager.resolve_confirmation(id, r),
434
+ [manager]
435
+ );
436
+ return { manager, active_jobs, submit_batch, get_job, get_jobs_for_group, resolve_confirmation };
437
+ }
438
+
439
+ // src/background_upload/react/use-job-status.ts
440
+ var import_react5 = require("react");
441
+ function useJobStatus(job_id) {
442
+ const manager = (0, import_react5.useContext)(FileUploadContext);
443
+ return (0, import_react5.useSyncExternalStore)(
444
+ (callback) => {
445
+ if (!manager || !job_id) return () => {
446
+ };
447
+ const unsubs = [
448
+ manager.on("job:status_changed", ({ job }) => {
449
+ if (job.job_id === job_id) callback();
450
+ }),
451
+ manager.on("job:progress", ({ job }) => {
452
+ if (job.job_id === job_id) callback();
453
+ }),
454
+ manager.on("job:completed", ({ job }) => {
455
+ if (job.job_id === job_id) callback();
456
+ }),
457
+ manager.on("job:error", ({ job }) => {
458
+ if (job.job_id === job_id) callback();
459
+ })
460
+ ];
461
+ return () => unsubs.forEach((u) => u());
462
+ },
463
+ () => manager && job_id ? manager.get_job(job_id) : null,
464
+ () => null
465
+ );
466
+ }
467
+ // Annotate the CommonJS export names for ESM import in node:
468
+ 0 && (module.exports = {
469
+ FileUploadContext,
470
+ HazoFileUploadProvider,
471
+ useFileUpload,
472
+ useFileUploadToasts,
473
+ useJobStatus
474
+ });