@vef-framework-react/core 2.1.12 → 2.2.1

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 (69) hide show
  1. package/dist/cjs/api/helpers.cjs +1 -1
  2. package/dist/cjs/index.cjs +1 -1
  3. package/dist/cjs/storage/errors.cjs +1 -0
  4. package/dist/cjs/storage/index.cjs +1 -0
  5. package/dist/cjs/storage/protocol.cjs +1 -0
  6. package/dist/cjs/storage/resumable/fingerprint.cjs +1 -0
  7. package/dist/cjs/storage/resumable/index.cjs +1 -0
  8. package/dist/cjs/storage/resumable/persistence.cjs +1 -0
  9. package/dist/cjs/storage/resumable/plan.cjs +1 -0
  10. package/dist/cjs/storage/types.cjs +1 -0
  11. package/dist/cjs/storage/uploader.cjs +1 -0
  12. package/dist/es/api/client.js +1 -1
  13. package/dist/es/api/constants.js +1 -1
  14. package/dist/es/api/helpers.js +19 -3
  15. package/dist/es/api/index.js +1 -1
  16. package/dist/es/auth/helpers.js +1 -1
  17. package/dist/es/auth/index.js +1 -1
  18. package/dist/es/context/api-client.js +1 -1
  19. package/dist/es/context/app.js +1 -1
  20. package/dist/es/context/context-selector.js +1 -1
  21. package/dist/es/context/disabled.js +1 -1
  22. package/dist/es/context/index.js +1 -1
  23. package/dist/es/dnd/index.js +1 -1
  24. package/dist/es/http/client.js +1 -1
  25. package/dist/es/http/constants.js +1 -1
  26. package/dist/es/http/errors.js +1 -1
  27. package/dist/es/http/helpers.js +1 -1
  28. package/dist/es/http/index.js +1 -1
  29. package/dist/es/immer/index.js +1 -1
  30. package/dist/es/index.js +28 -21
  31. package/dist/es/motion/features.js +1 -1
  32. package/dist/es/motion/index.js +1 -1
  33. package/dist/es/motion/motion-provider.js +1 -1
  34. package/dist/es/query/constants.js +1 -1
  35. package/dist/es/query/helpers.js +1 -1
  36. package/dist/es/query/hooks.js +1 -1
  37. package/dist/es/query/index.js +1 -1
  38. package/dist/es/sse/client.js +1 -1
  39. package/dist/es/sse/helpers.js +1 -1
  40. package/dist/es/sse/index.js +1 -1
  41. package/dist/es/state/index.js +1 -1
  42. package/dist/es/state-machine/index.js +1 -1
  43. package/dist/es/storage/errors.js +24 -0
  44. package/dist/es/storage/index.js +8 -0
  45. package/dist/es/storage/protocol.js +55 -0
  46. package/dist/es/storage/resumable/fingerprint.js +25 -0
  47. package/dist/es/storage/resumable/index.js +4 -0
  48. package/dist/es/storage/resumable/persistence.js +46 -0
  49. package/dist/es/storage/resumable/plan.js +35 -0
  50. package/dist/es/storage/types.js +5 -0
  51. package/dist/es/storage/uploader.js +223 -0
  52. package/dist/es/store/bound.js +1 -1
  53. package/dist/es/store/index.js +1 -1
  54. package/dist/es/store/unbound.js +1 -1
  55. package/dist/es/store/use-deep.js +1 -1
  56. package/dist/types/api/helpers.d.ts +16 -1
  57. package/dist/types/api/index.d.ts +1 -1
  58. package/dist/types/api/types.d.ts +30 -0
  59. package/dist/types/index.d.ts +2 -1
  60. package/dist/types/storage/errors.d.ts +46 -0
  61. package/dist/types/storage/index.d.ts +5 -0
  62. package/dist/types/storage/protocol.d.ts +102 -0
  63. package/dist/types/storage/resumable/fingerprint.d.ts +52 -0
  64. package/dist/types/storage/resumable/index.d.ts +3 -0
  65. package/dist/types/storage/resumable/persistence.d.ts +98 -0
  66. package/dist/types/storage/resumable/plan.d.ts +105 -0
  67. package/dist/types/storage/types.d.ts +166 -0
  68. package/dist/types/storage/uploader.d.ts +49 -0
  69. package/package.json +9 -9
@@ -0,0 +1,35 @@
1
+ /*! @vef-framework-react/core v2.2.1 made by Venus | 2026-05-18T08:55:53.533Z */
2
+ import { abortUpload as e, listParts as t } from "../protocol.js";
3
+ //#region src/storage/resumable/plan.ts
4
+ async function n(n) {
5
+ let { fingerprint: a, persistence: o, ctx: s, onResumeDetected: c = r, signal: l } = n, u = await o.load(a);
6
+ if (!u) return { kind: "fresh" };
7
+ if (i(u.expiresAt)) return await o.remove(a), { kind: "fresh" };
8
+ let d;
9
+ try {
10
+ d = (await t(s, u.claimId, l)).parts;
11
+ } catch {
12
+ return await o.remove(a), { kind: "fresh" };
13
+ }
14
+ return (await c({
15
+ record: u,
16
+ completedParts: d
17
+ })).kind === "discard" ? (await o.remove(a), await e(s, u.claimId), { kind: "fresh" }) : {
18
+ kind: "resume",
19
+ claimId: u.claimId,
20
+ key: u.key,
21
+ partSize: u.partSize,
22
+ partCount: u.partCount,
23
+ expiresAt: u.expiresAt,
24
+ completedParts: d
25
+ };
26
+ }
27
+ function r() {
28
+ return Promise.resolve({ kind: "discard" });
29
+ }
30
+ function i(e) {
31
+ let t = Date.parse(e);
32
+ return Number.isNaN(t) ? !0 : t <= Date.now();
33
+ }
34
+ //#endregion
35
+ export { n as resolveResumePlan };
@@ -0,0 +1,5 @@
1
+ /*! @vef-framework-react/core v2.2.1 made by Venus | 2026-05-18T08:55:53.533Z */
2
+ //#region src/storage/types.ts
3
+ var e = "pub/", t = "priv/";
4
+ //#endregion
5
+ export { t as PRIVATE_PREFIX, e as PUBLIC_PREFIX };
@@ -0,0 +1,223 @@
1
+ /*! @vef-framework-react/core v2.2.1 made by Venus | 2026-05-18T08:55:53.533Z */
2
+ import { UploadAbortedError as e, UploadError as t, UploadPartError as n, UploadProtocolError as r } from "./errors.js";
3
+ import { abortUpload as i, completeUpload as a, initUpload as o, uploadPart as s } from "./protocol.js";
4
+ //#region src/storage/uploader.ts
5
+ var c = 3, l = 3, u = 500, d = 8e3;
6
+ function f(e, t, n, r) {
7
+ return n < r ? t : e - t * (r - 1);
8
+ }
9
+ function p(t, n) {
10
+ return new Promise((r, i) => {
11
+ if (n.aborted) {
12
+ i(new e());
13
+ return;
14
+ }
15
+ let a = setTimeout(() => {
16
+ n.removeEventListener("abort", o), r();
17
+ }, t), o = () => {
18
+ clearTimeout(a), n.removeEventListener("abort", o), i(new e());
19
+ };
20
+ n.addEventListener("abort", o, { once: !0 });
21
+ });
22
+ }
23
+ var m = class {
24
+ #e;
25
+ #t;
26
+ #n;
27
+ #r;
28
+ #i;
29
+ #a;
30
+ #o;
31
+ #s;
32
+ #c;
33
+ #l;
34
+ #u;
35
+ #d;
36
+ #f;
37
+ #p;
38
+ #m = "idle";
39
+ #h;
40
+ #g;
41
+ #_ = 0;
42
+ #v = /* @__PURE__ */ new Map();
43
+ #y = 1;
44
+ #b = /* @__PURE__ */ new Set();
45
+ constructor(e, n, r = {}) {
46
+ let i = r.init?.filename ?? (n instanceof File ? n.name : "");
47
+ if (!i) throw new t("Uploader requires a filename (set options.init.filename for Blob input)");
48
+ this.#e = {
49
+ http: e,
50
+ apiPath: r.apiPath ?? "/api",
51
+ resource: r.resource ?? "sys/storage",
52
+ version: r.version ?? "v1"
53
+ }, this.#t = n, this.#n = i, this.#r = r.init?.contentType ?? (n instanceof File && n.type || void 0), this.#i = r.init?.public, this.#a = Math.max(1, r.partConcurrency ?? c), this.#o = Math.max(1, r.maxPartRetries ?? l), this.#s = Math.max(0, r.retryBaseDelay ?? u), this.#c = Math.max(this.#s, r.retryMaxDelay ?? d), this.#l = r.onProgress, this.#u = r.onStatusChange, this.#d = r.onSessionOpened, this.#f = new AbortController(), this.#p = this.#x(r.signal), this.#h = {
54
+ loaded: 0,
55
+ total: n.size,
56
+ partsCompleted: 0,
57
+ partsTotal: 0,
58
+ percent: 0
59
+ };
60
+ }
61
+ get status() {
62
+ return this.#m;
63
+ }
64
+ get progress() {
65
+ return this.#h;
66
+ }
67
+ start(e) {
68
+ return this.#g ||= this.#E(e ?? { kind: "fresh" }), this.#g;
69
+ }
70
+ async abort() {
71
+ if (this.#S()) return;
72
+ this.#f.abort();
73
+ let e = this.#g;
74
+ if (e) try {
75
+ await e;
76
+ } catch {}
77
+ }
78
+ #x(e) {
79
+ if (!e) return;
80
+ if (e.aborted) {
81
+ this.#f.abort();
82
+ return;
83
+ }
84
+ let t = () => this.#f.abort();
85
+ return e.addEventListener("abort", t, { once: !0 }), () => e.removeEventListener("abort", t);
86
+ }
87
+ #S() {
88
+ return this.#m === "succeeded" || this.#m === "failed" || this.#m === "aborted";
89
+ }
90
+ #C(e) {
91
+ this.#m !== e && (this.#m = e, this.#u?.(e));
92
+ }
93
+ #w() {
94
+ let e = 0;
95
+ for (let t of this.#v.values()) e += t;
96
+ let t = Math.min(this.#h.total, this.#_ + e), n = this.#h.total === 0 ? 0 : Math.floor(t / this.#h.total * 100);
97
+ this.#h = {
98
+ ...this.#h,
99
+ loaded: t,
100
+ percent: n
101
+ }, this.#l?.(this.#h);
102
+ }
103
+ async #T(e) {
104
+ if (e.kind === "fresh") return {
105
+ session: await o(this.#e, {
106
+ filename: this.#n,
107
+ size: this.#t.size,
108
+ contentType: this.#r,
109
+ public: this.#i
110
+ }, this.#f.signal),
111
+ skipParts: /* @__PURE__ */ new Set(),
112
+ completedBytes: 0
113
+ };
114
+ let t = e.partSize * e.partCount, n = e.partSize * (e.partCount - 1) + 1;
115
+ if (this.#t.size < n || this.#t.size > t) throw new r("resume", `file size ${this.#t.size} does not fit the resumed session (partSize=${e.partSize}, partCount=${e.partCount})`);
116
+ let i = /* @__PURE__ */ new Set(), a = 0;
117
+ for (let t of e.completedParts) i.add(t.partNumber), a += t.size;
118
+ return {
119
+ session: {
120
+ key: e.key,
121
+ claimId: e.claimId,
122
+ originalFilename: this.#n,
123
+ partSize: e.partSize,
124
+ partCount: e.partCount,
125
+ expiresAt: e.expiresAt
126
+ },
127
+ skipParts: i,
128
+ completedBytes: a
129
+ };
130
+ }
131
+ async #E(t) {
132
+ let n;
133
+ try {
134
+ if (this.#f.signal.aborted) throw new e();
135
+ this.#C("initializing");
136
+ let { session: r, skipParts: i, completedBytes: o } = await this.#T(t);
137
+ ({claimId: n} = r), this.#b = i, this.#_ = o, this.#d?.({
138
+ claimId: r.claimId,
139
+ key: r.key,
140
+ partSize: r.partSize,
141
+ partCount: r.partCount,
142
+ expiresAt: r.expiresAt
143
+ }), this.#h = {
144
+ ...this.#h,
145
+ partsTotal: r.partCount,
146
+ partsCompleted: i.size
147
+ }, this.#w(), this.#C("uploading"), await this.#D(r), this.#C("completing");
148
+ let s = await a(this.#e, r.claimId, this.#f.signal);
149
+ return this.#C("succeeded"), {
150
+ bucket: s.bucket,
151
+ key: s.key,
152
+ eTag: s.eTag,
153
+ size: s.size,
154
+ contentType: s.contentType,
155
+ lastModified: s.lastModified,
156
+ originalFilename: s.originalFilename,
157
+ metadata: s.metadata
158
+ };
159
+ } catch (t) {
160
+ let r = t instanceof e;
161
+ throw r && this.#C("aborting"), r && n !== void 0 && await i(this.#e, n), this.#C(r ? "aborted" : "failed"), this.#j(t);
162
+ } finally {
163
+ this.#p?.();
164
+ }
165
+ }
166
+ async #D(e) {
167
+ let t = Math.min(this.#a, e.partCount), n = [];
168
+ for (let r = 0; r < t; r++) n.push(this.#O(e));
169
+ try {
170
+ await Promise.all(n);
171
+ } catch (e) {
172
+ throw this.#f.abort(), e;
173
+ }
174
+ }
175
+ async #O(e) {
176
+ for (; !this.#f.signal.aborted;) {
177
+ let t = this.#k(e.partCount);
178
+ if (t === null) return;
179
+ await this.#A(e, t);
180
+ }
181
+ }
182
+ #k(e) {
183
+ for (; this.#y <= e;) {
184
+ let e = this.#y++;
185
+ if (!this.#b.has(e)) return e;
186
+ }
187
+ return null;
188
+ }
189
+ async #A(t, r) {
190
+ let i = f(this.#t.size, t.partSize, r, t.partCount), a = (r - 1) * t.partSize, o = this.#t.slice(a, a + i), c = 0, l;
191
+ for (; c < this.#o;) {
192
+ if (c++, this.#f.signal.aborted) throw new e();
193
+ try {
194
+ this.#v.set(r, 0), await s(this.#e, {
195
+ claimId: t.claimId,
196
+ partNumber: r,
197
+ blob: o,
198
+ onProgress: (e) => {
199
+ this.#v.set(r, Math.min(i, e.loaded)), this.#w();
200
+ },
201
+ signal: this.#f.signal
202
+ }), this.#v.delete(r), this.#_ += i, this.#h = {
203
+ ...this.#h,
204
+ partsCompleted: this.#h.partsCompleted + 1
205
+ }, this.#w();
206
+ return;
207
+ } catch (t) {
208
+ if (this.#v.delete(r), l = t, this.#f.signal.aborted) throw new e();
209
+ if (c >= this.#o) break;
210
+ await p(Math.min(this.#c, this.#s * 2 ** (c - 1)), this.#f.signal);
211
+ }
212
+ }
213
+ throw new n(r, c, { cause: l });
214
+ }
215
+ #j(e) {
216
+ return e instanceof t ? e : e instanceof Error ? new r("unknown", e.message, { cause: e }) : new r("unknown", String(e));
217
+ }
218
+ };
219
+ function h(e, t, n) {
220
+ return new m(e, t, n).start();
221
+ }
222
+ //#endregion
223
+ export { m as Uploader, h as uploadFile };
@@ -1,4 +1,4 @@
1
- /*! @vef-framework-react/core v2.1.12 made by Venus | 2026-05-07T08:00:43.953Z */
1
+ /*! @vef-framework-react/core v2.2.1 made by Venus | 2026-05-18T08:55:53.533Z */
2
2
  import { constantCase as e, identity as t } from "@vef-framework-react/shared";
3
3
  import { create as n } from "zustand";
4
4
  import { createJSONStorage as r, persist as i, subscribeWithSelector as a } from "zustand/middleware";
@@ -1,4 +1,4 @@
1
- /*! @vef-framework-react/core v2.1.12 made by Venus | 2026-05-07T08:00:43.953Z */
1
+ /*! @vef-framework-react/core v2.2.1 made by Venus | 2026-05-18T08:55:53.533Z */
2
2
  import "./bound.js";
3
3
  import "./unbound.js";
4
4
  import "./use-deep.js";
@@ -1,4 +1,4 @@
1
- /*! @vef-framework-react/core v2.1.12 made by Venus | 2026-05-07T08:00:43.953Z */
1
+ /*! @vef-framework-react/core v2.2.1 made by Venus | 2026-05-18T08:55:53.533Z */
2
2
  import { constantCase as e, identity as t, isPlainObject as n, mergeWith as r } from "@vef-framework-react/shared";
3
3
  import { createContext as i, use as a, useRef as o } from "react";
4
4
  import { jsx as s } from "@emotion/react/jsx-runtime";
@@ -1,4 +1,4 @@
1
- /*! @vef-framework-react/core v2.1.12 made by Venus | 2026-05-07T08:00:43.953Z */
1
+ /*! @vef-framework-react/core v2.2.1 made by Venus | 2026-05-18T08:55:53.533Z */
2
2
  import { isDeepEqual as e } from "@vef-framework-react/shared";
3
3
  import { useRef as t } from "react";
4
4
  //#region src/store/use-deep.ts
@@ -1,4 +1,4 @@
1
- import { ApiClientOptions } from './types';
1
+ import { ApiClientOptions, ApiRequest } from './types';
2
2
  import { ApiClient } from './client';
3
3
  /**
4
4
  * Creates a new API client.
@@ -7,3 +7,18 @@ import { ApiClient } from './client';
7
7
  * @returns The API client.
8
8
  */
9
9
  export declare function createApiClient(options: ApiClientOptions): ApiClient;
10
+ /**
11
+ * Build an `ApiRequest` envelope. The three-arg form defaults `version` to
12
+ * `"v1"`; the four-arg form lets callers pin a non-default version.
13
+ *
14
+ * @example Default version
15
+ * ```ts
16
+ * createApiRequest("sys/storage", "abort_upload", { claimId });
17
+ * ```
18
+ * @example Explicit version
19
+ * ```ts
20
+ * createApiRequest("sys/storage", "init_upload", "v2", { filename, size });
21
+ * ```
22
+ */
23
+ export declare function createApiRequest<P extends object, M extends object>(resource: string, action: string, params?: P, meta?: M): ApiRequest<P, M>;
24
+ export declare function createApiRequest<P extends object, M extends object>(resource: string, action: string, version: string, params?: P, meta?: M): ApiRequest<P, M>;
@@ -1,4 +1,4 @@
1
1
  export { ApiClient } from './client';
2
2
  export { HTTP_CLIENT, QUERY_CLIENT } from './constants';
3
- export { createApiClient } from './helpers';
3
+ export { createApiClient, createApiRequest } from './helpers';
4
4
  export type * from './types';
@@ -46,3 +46,33 @@ export interface MutationFunction<TData = unknown, TParams = never> extends Muta
46
46
  */
47
47
  key: Key;
48
48
  }
49
+ /**
50
+ * The wire-level envelope every framework RPC call shares. `resource` and
51
+ * `action` identify the target operation on the Go side; `version` pins the
52
+ * action's compatibility profile (defaults to `v1`); `params` and `meta`
53
+ * carry the per-call payload and metadata.
54
+ */
55
+ export interface ApiRequest<P extends object = Record<string, unknown>, M extends object = Record<string, unknown>> {
56
+ /**
57
+ * The resource the action targets (e.g. `"sys/storage"`).
58
+ */
59
+ resource: string;
60
+ /**
61
+ * The action to invoke on the resource (e.g. `"init_upload"`).
62
+ */
63
+ action: string;
64
+ /**
65
+ * The action's compatibility version. Defaults to `"v1"` when omitted at
66
+ * the helper level.
67
+ */
68
+ version: string;
69
+ /**
70
+ * Per-call parameters. Shape is defined by the target action.
71
+ */
72
+ params?: P;
73
+ /**
74
+ * Per-call metadata (e.g. pagination). Shape is defined by the target
75
+ * action.
76
+ */
77
+ meta?: M;
78
+ }
@@ -1,4 +1,4 @@
1
- export { ApiClient, createApiClient, HTTP_CLIENT, QUERY_CLIENT, type ApiClientOptions, type MutationFunction, type QueryFunction, type QueryKey } from './api';
1
+ export { ApiClient, createApiClient, createApiRequest, HTTP_CLIENT, QUERY_CLIENT, type ApiClientOptions, type ApiRequest, type MutationFunction, type QueryFunction, type QueryKey } from './api';
2
2
  export { checkPermission, type PermissionCheckMode } from './auth';
3
3
  export { type DataOption, type DataOptionWithPinyin, type PaginationParams, type PaginationResult } from './common';
4
4
  export { ApiClientProvider, AppContextProvider, createContextWithSelector, DisabledProvider, useApiClient, useAppContext, useDisabled, type AppContext, type SelectorContextProviderProps, type SelectorContextResult, type UseSelectorContext } from './context';
@@ -11,5 +11,6 @@ export { keepPreviousData, matchMutation, matchQuery, skipQueryToken, useInfinit
11
11
  export { createSseClient, SseClient, type SseClientOptions, type SseEventHandlers, type SseMessageEvent, type SseRequestConfig } from './sse';
12
12
  export { atom, AtomStoreProvider, createAtomStore, getDefaultAtomStore, useAtom, useAtomStore, useAtomValue, useSetAtom, type Atom, type AtomGetter, type AtomSetter, type ExtractAtomArgs, type ExtractAtomResult, type ExtractAtomValue, type PrimitiveAtom, type SetStateAction, type WritableAtom } from './state';
13
13
  export { Actor, createActor, createMachine, updateContext, useActor, useActorRef, type ActorLogic, type ActorOptions, type AnyActorLogic, type AnyMachineSnapshot, type AnyStateMachine, type MachineConfig, type MachineContext, type MachineSnapshot, type RequiredActorOptionsKeys, type SnapshotFrom, type StateMachine } from './state-machine';
14
+ export { abortUpload, completeUpload, initUpload, listParts, LocalStoragePersistence, PrefixFingerprinter, PRIVATE_PREFIX, PUBLIC_PREFIX, resolveResumePlan, DEFAULT_API_PATH as STORAGE_API_PATH, DEFAULT_RESOURCE as STORAGE_RESOURCE, DEFAULT_VERSION as STORAGE_VERSION, UploadAbortedError, Uploader, UploadError, uploadFile, uploadPart, UploadPartError, UploadProtocolError, WeakFingerprinter, type CompleteUploadResponse, type FileFingerprinter, type InitUploadParams, type InitUploadResponse, type ListedPart, type ListPartsResponse, type ObjectInfo, type ProtocolContext, type ResolveResumeInputs, type ResumablePersistence, type ResumeCandidate, type ResumeDecision, type ResumeDecisionHandler, type ResumePlan, type ResumeRecord, type UploaderOptions, type UploadInit, type UploadPartResponse, type UploadProgress, type UploadResult, type UploadSessionSnapshot, type UploadStatus } from './storage';
14
15
  export { createComponentStore, createPersistedStore, createStore, useDeep, useShallow, type PersistenceOptions, type ComponentStoreResult as ReturnedComponentStoreResult, type SliceStateCreator, type StoreProviderProps, type UnboundStore, type UseBoundStore, type UseBoundStoreWithPersist, type UseStore } from './store';
15
16
  export { clsx, type ClassArray, type ClassDictionary, type ClassValue } from 'clsx';
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Base class for every error surfaced by the storage upload pipeline.
3
+ * Tests and callers should use `instanceof UploadError` to discriminate
4
+ * uploader failures from unrelated runtime errors.
5
+ */
6
+ export declare class UploadError extends Error {
7
+ constructor(message: string, options?: ErrorOptions);
8
+ }
9
+ /**
10
+ * Thrown when the caller (or external signal) cancels an in-flight upload.
11
+ * Surfaces from the `start()` promise so callers can distinguish a manual
12
+ * cancellation from a genuine protocol failure.
13
+ */
14
+ export declare class UploadAbortedError extends UploadError {
15
+ constructor(message?: string);
16
+ }
17
+ /**
18
+ * Thrown when the backend rejects a protocol RPC (init_upload,
19
+ * complete_upload, abort_upload) in a way that is not recoverable through
20
+ * the upload's own retry mechanism.
21
+ *
22
+ * `cause` preserves the underlying axios / `BusinessError` for caller
23
+ * diagnostics; do not parse `cause` for control flow.
24
+ */
25
+ export declare class UploadProtocolError extends UploadError {
26
+ /**
27
+ * The protocol action that produced the error.
28
+ */
29
+ readonly action: string;
30
+ constructor(action: string, message: string, options?: ErrorOptions);
31
+ }
32
+ /**
33
+ * Thrown when a single part fails repeatedly past `maxPartRetries`. Carries
34
+ * the part number so the caller can surface a specific failure to the user.
35
+ */
36
+ export declare class UploadPartError extends UploadError {
37
+ /**
38
+ * The 1-indexed part number that failed.
39
+ */
40
+ readonly partNumber: number;
41
+ /**
42
+ * The number of attempts made before giving up.
43
+ */
44
+ readonly attempts: number;
45
+ constructor(partNumber: number, attempts: number, options?: ErrorOptions);
46
+ }
@@ -0,0 +1,5 @@
1
+ export { UploadAbortedError, UploadError, UploadPartError, UploadProtocolError } from './errors';
2
+ export { abortUpload, completeUpload, DEFAULT_API_PATH, DEFAULT_RESOURCE, DEFAULT_VERSION, initUpload, listParts, uploadPart, type CompleteUploadResponse, type InitUploadParams, type InitUploadResponse, type ListedPart, type ListPartsResponse, type ObjectInfo, type ProtocolContext, type UploadPartResponse } from './protocol';
3
+ export { LocalStoragePersistence, PrefixFingerprinter, resolveResumePlan, WeakFingerprinter, type FileFingerprinter, type ResolveResumeInputs, type ResumablePersistence, type ResumeCandidate, type ResumeDecision, type ResumeDecisionHandler, type ResumePlan, type ResumeRecord } from './resumable';
4
+ export { PRIVATE_PREFIX, PUBLIC_PREFIX, type UploaderOptions, type UploadInit, type UploadProgress, type UploadResult, type UploadSessionSnapshot, type UploadStatus } from './types';
5
+ export { Uploader, uploadFile } from './uploader';
@@ -0,0 +1,102 @@
1
+ import { AxiosProgressEvent, GenericAbortSignal } from 'axios';
2
+ import { HttpClient } from '../http';
3
+ /**
4
+ * Default RPC entrypoint, resource, and version. Match the conventions used
5
+ * by `vef-framework-go`'s storage resource.
6
+ */
7
+ export declare const DEFAULT_API_PATH = "/api";
8
+ export declare const DEFAULT_RESOURCE = "sys/storage";
9
+ export declare const DEFAULT_VERSION = "v1";
10
+ /**
11
+ * Shared identifying fields for every storage RPC call. The protocol allows
12
+ * overriding `resource` / `version` per call site so callers running against
13
+ * a customized backend resource name stay supported without re-wiring the
14
+ * Uploader.
15
+ */
16
+ export interface ProtocolContext {
17
+ http: Readonly<HttpClient>;
18
+ apiPath: string;
19
+ resource: string;
20
+ version: string;
21
+ }
22
+ export interface InitUploadParams {
23
+ filename: string;
24
+ size: number;
25
+ contentType?: string;
26
+ public?: boolean;
27
+ }
28
+ export interface InitUploadResponse {
29
+ key: string;
30
+ claimId: string;
31
+ originalFilename: string;
32
+ partSize: number;
33
+ partCount: number;
34
+ expiresAt: string;
35
+ }
36
+ export interface UploadPartResponse {
37
+ partNumber: number;
38
+ size: number;
39
+ }
40
+ export interface ObjectInfo {
41
+ bucket: string;
42
+ key: string;
43
+ eTag: string;
44
+ size: number;
45
+ contentType: string;
46
+ lastModified: string;
47
+ metadata?: Record<string, string>;
48
+ }
49
+ export interface CompleteUploadResponse extends ObjectInfo {
50
+ originalFilename: string;
51
+ }
52
+ /**
53
+ * Open a multipart upload session on the backend. The returned `partSize`
54
+ * and `partCount` are authoritative — callers must slice the file using
55
+ * exactly these values.
56
+ */
57
+ export declare function initUpload(ctx: ProtocolContext, params: InitUploadParams, signal?: GenericAbortSignal): Promise<InitUploadResponse>;
58
+ /**
59
+ * Upload a single part. Streams the blob through `HttpClient.upload` so
60
+ * axios surfaces progress events for the in-flight transfer.
61
+ */
62
+ export declare function uploadPart(ctx: ProtocolContext, args: {
63
+ claimId: string;
64
+ partNumber: number;
65
+ blob: Blob;
66
+ onProgress?: (event: AxiosProgressEvent) => void;
67
+ signal?: GenericAbortSignal;
68
+ }): Promise<UploadPartResponse>;
69
+ /**
70
+ * One row of `list_parts`. Mirrors the backend's `ListedPart` struct —
71
+ * ETag is intentionally omitted server-side and therefore never appears
72
+ * here either: complete_upload assembles the parts list from the
73
+ * database, not from client-supplied state.
74
+ */
75
+ export interface ListedPart {
76
+ partNumber: number;
77
+ size: number;
78
+ }
79
+ export interface ListPartsResponse {
80
+ parts: ListedPart[];
81
+ }
82
+ /**
83
+ * Enumerate parts the backend has already accepted for an active claim.
84
+ * Drives the resume path: callers feed the result into a `ResumePlan`
85
+ * so the Uploader knows which slots to skip. Errors map to
86
+ * `UploadProtocolError("list_parts", ...)` so callers can fold the
87
+ * "cannot resume" case into the storage error hierarchy and fall back
88
+ * to a fresh init.
89
+ */
90
+ export declare function listParts(ctx: ProtocolContext, claimId: string, signal?: GenericAbortSignal): Promise<ListPartsResponse>;
91
+ /**
92
+ * Finalize a multipart session. The backend reconstructs the part list from
93
+ * its `upload_part` table — the client never replays ETags.
94
+ */
95
+ export declare function completeUpload(ctx: ProtocolContext, claimId: string, signal?: GenericAbortSignal): Promise<CompleteUploadResponse>;
96
+ /**
97
+ * Best-effort cancellation of an in-flight session. Errors are swallowed —
98
+ * `abort_upload` is idempotent on the server, and a failure here must not
99
+ * mask the original cause that triggered the abort. Caller code can log via
100
+ * the optional `onError` hook.
101
+ */
102
+ export declare function abortUpload(ctx: ProtocolContext, claimId: string, onError?: (error: unknown) => void): Promise<void>;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Stable identifier for a file. Two `File` instances should produce the
3
+ * same fingerprint iff a sensible resume policy can treat them as the
4
+ * "same upload" — name, size, lastModified, and (for the default
5
+ * implementation) the first 4 MiB of content.
6
+ *
7
+ * The fingerprint is used as a `ResumablePersistence` lookup key. It is
8
+ * never sent to the server: resume detection is purely client-side, and
9
+ * the backend's authority is the claim ID stored alongside the
10
+ * fingerprint, not the fingerprint itself.
11
+ */
12
+ export interface FileFingerprinter {
13
+ fingerprint: (file: File) => Promise<string>;
14
+ }
15
+ /**
16
+ * Fast, deterministic fingerprint derived from `name`, `size`, and
17
+ * `lastModified`. Synchronous (wrapped in a resolved promise) and
18
+ * always available — no `crypto.subtle`, no I/O.
19
+ *
20
+ * Trade-off: a user who replaces a file with different content but
21
+ * keeps the same name, size, and mtime will collide. Use only when
22
+ * the surrounding UI guarantees file identity by other means (e.g.
23
+ * a controlled upload widget that locks the file once selected).
24
+ */
25
+ export declare class WeakFingerprinter implements FileFingerprinter {
26
+ fingerprint(file: File): Promise<string>;
27
+ }
28
+ /**
29
+ * Default fingerprinter for the framework. Combines metadata
30
+ * (name + size + lastModified) with the SHA-256 of the file's first
31
+ * 4 MiB. Catches the common "same metadata, different content" case
32
+ * (e.g. saving a new export over an old one) while keeping the hash
33
+ * window small enough to run on the main thread (<50 ms for large
34
+ * files) — no Web Worker needed.
35
+ *
36
+ * Requires `crypto.subtle`, which is available in all modern browsers
37
+ * over HTTPS (including `localhost`). When `crypto.subtle` is not
38
+ * available the constructor throws synchronously — callers should
39
+ * fall back to `WeakFingerprinter` at that point.
40
+ */
41
+ export declare class PrefixFingerprinter implements FileFingerprinter {
42
+ #private;
43
+ static readonly defaultPrefixBytes: number;
44
+ /**
45
+ * @param prefixBytes - Override the prefix window size. The default
46
+ * (4 MiB) is a balance between collision resistance and main-thread
47
+ * cost; reducing it makes fingerprinting faster but more likely to
48
+ * miss small content changes near the file's tail.
49
+ */
50
+ constructor(prefixBytes?: number);
51
+ fingerprint(file: File): Promise<string>;
52
+ }
@@ -0,0 +1,3 @@
1
+ export { PrefixFingerprinter, WeakFingerprinter, type FileFingerprinter } from './fingerprint';
2
+ export { LocalStoragePersistence, type ResumablePersistence, type ResumeRecord } from './persistence';
3
+ export { resolveResumePlan, type ResolveResumeInputs, type ResumeCandidate, type ResumeDecision, type ResumeDecisionHandler, type ResumePlan } from './plan';