lemon-model 1.2.4 → 1.4.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 (36) hide show
  1. package/dist/esm/upload/engine/engine.js +166 -0
  2. package/dist/esm/upload/engine/engine.js.map +1 -0
  3. package/dist/esm/upload/engine/executors.js +57 -0
  4. package/dist/esm/upload/engine/executors.js.map +1 -0
  5. package/dist/esm/upload/engine/index.js +13 -0
  6. package/dist/esm/upload/engine/index.js.map +1 -0
  7. package/dist/esm/upload/engine/progress.js +52 -0
  8. package/dist/esm/upload/engine/progress.js.map +1 -0
  9. package/dist/esm/upload/index.js +11 -0
  10. package/dist/esm/upload/index.js.map +1 -0
  11. package/dist/esm/upload/testing.js +73 -0
  12. package/dist/esm/upload/testing.js.map +1 -0
  13. package/dist/esm/upload/types.js +69 -0
  14. package/dist/esm/upload/types.js.map +1 -0
  15. package/dist/upload/engine/engine.d.ts +52 -0
  16. package/dist/upload/engine/engine.js +172 -0
  17. package/dist/upload/engine/engine.js.map +1 -0
  18. package/dist/upload/engine/executors.d.ts +44 -0
  19. package/dist/upload/engine/executors.js +62 -0
  20. package/dist/upload/engine/executors.js.map +1 -0
  21. package/dist/upload/engine/index.d.ts +12 -0
  22. package/dist/upload/engine/index.js +29 -0
  23. package/dist/upload/engine/index.js.map +1 -0
  24. package/dist/upload/engine/progress.d.ts +48 -0
  25. package/dist/upload/engine/progress.js +57 -0
  26. package/dist/upload/engine/progress.js.map +1 -0
  27. package/dist/upload/index.d.ts +10 -0
  28. package/dist/upload/index.js +27 -0
  29. package/dist/upload/index.js.map +1 -0
  30. package/dist/upload/testing.d.ts +21 -0
  31. package/dist/upload/testing.js +76 -0
  32. package/dist/upload/testing.js.map +1 -0
  33. package/dist/upload/types.d.ts +230 -0
  34. package/dist/upload/types.js +74 -0
  35. package/dist/upload/types.js.map +1 -0
  36. package/package.json +26 -1
@@ -0,0 +1,166 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { UPLOAD_FAILURE_CODE, UPLOAD_FAILURE_SOURCE } from '../types.js';
11
+ import { UploadProgressTracker } from './progress.js';
12
+ /**
13
+ * transfers in flight at once.
14
+ * - a browser queues beyond ~6 per origin, and a queued presigned url burns its `expiresAt` while it waits.
15
+ * - override per engine when a shell measures something better.
16
+ */
17
+ export const UPLOAD_DEFAULT_CONCURRENCY = 3;
18
+ /** run `task` over 0..count-1 with at most `limit` in flight; results keep input order */
19
+ const runPooled = (count, limit, task) => __awaiter(void 0, void 0, void 0, function* () {
20
+ const results = new Array(count);
21
+ let next = 0;
22
+ const worker = () => __awaiter(void 0, void 0, void 0, function* () {
23
+ for (;;) {
24
+ const index = next++;
25
+ if (index >= count)
26
+ return;
27
+ results[index] = yield task(index);
28
+ }
29
+ });
30
+ const size = Math.max(1, Math.min(limit, count));
31
+ yield Promise.all(new Array(size).fill(0).map(() => worker()));
32
+ return results;
33
+ });
34
+ const asBody = (source) => __awaiter(void 0, void 0, void 0, function* () {
35
+ return ({
36
+ name: source.name,
37
+ contentType: source.contentType,
38
+ contentSize: source.contentSize,
39
+ hash: source.hash ? yield source.hash() : undefined,
40
+ });
41
+ });
42
+ export class UploadEngine {
43
+ constructor(service, executors, options) {
44
+ this.service = service;
45
+ this.executors = executors;
46
+ this.options = options;
47
+ this.kinds = executors.map(executor => executor.kind);
48
+ }
49
+ /** returns one view per source, in order: `stored`, or `failed` with the reason. `onProgress` gets a snapshot per change */
50
+ upload(sources, onProgress) {
51
+ var _a, _b;
52
+ return __awaiter(this, void 0, void 0, function* () {
53
+ const tracker = new UploadProgressTracker(sources.map(source => source.contentSize), onProgress);
54
+ tracker.publish();
55
+ const list = yield Promise.all(sources.map(asBody));
56
+ const { list: tickets } = yield this.service.start({ list, transfers: this.kinds });
57
+ const limit = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.concurrency) !== null && _b !== void 0 ? _b : UPLOAD_DEFAULT_CONCURRENCY;
58
+ const receipts = yield runPooled(tickets.length, limit, i => this.transferTracked(tickets[i], sources[i], tracker, i));
59
+ // a server that broke rule 1 (shorter list) must not leave the bar hanging
60
+ sources.forEach((_, i) => tracker.settle(i));
61
+ const pending = receipts.filter((item) => !!item);
62
+ const settled = pending.length ? (yield this.service.complete({ list: pending })).list : [];
63
+ const byId = new Map(settled.filter(view => !!view.id).map((view) => [view.id, view]));
64
+ return tickets.map(ticket => (ticket.upload.id && byId.get(ticket.upload.id)) || ticket.upload);
65
+ });
66
+ }
67
+ transferTracked(ticket, source, tracker, index) {
68
+ return __awaiter(this, void 0, void 0, function* () {
69
+ try {
70
+ return yield this.transfer(ticket, source, tracker.sink(index));
71
+ }
72
+ catch (error) {
73
+ // an executor that throws (a CORS preflight failure surfaces as one) must not take the batch down
74
+ const id = ticket.upload.id;
75
+ if (!id)
76
+ return undefined;
77
+ const message = error instanceof Error ? error.message : String(error);
78
+ return {
79
+ id,
80
+ failure: { source: UPLOAD_FAILURE_SOURCE.client, code: UPLOAD_FAILURE_CODE.unknown, message },
81
+ };
82
+ }
83
+ finally {
84
+ tracker.settle(index);
85
+ }
86
+ });
87
+ }
88
+ transfer(ticket, source, onProgress) {
89
+ return __awaiter(this, void 0, void 0, function* () {
90
+ const id = ticket.upload.id;
91
+ // rejected at start: nothing was created, nothing to complete
92
+ if (!id)
93
+ return undefined;
94
+ // no instruction: already stored (dedup) — still confirmed through complete
95
+ if (!ticket.transfer)
96
+ return { id };
97
+ const executor = this.executors.find(candidate => { var _a; return candidate.kind === ((_a = ticket.transfer) === null || _a === void 0 ? void 0 : _a.kind); });
98
+ if (!executor)
99
+ return { id, failure: { source: UPLOAD_FAILURE_SOURCE.client, code: UPLOAD_FAILURE_CODE.notAcceptable } };
100
+ // NOTE: `ticket.transfer` must not be logged or stored — it may carry a credential url
101
+ return executor.run(id, ticket.transfer, source, onProgress);
102
+ });
103
+ }
104
+ }
105
+ /** lemon-core error convention: "<status> <LABEL> - <detail>" (status 0 = no response, XHR convention) */
106
+ const API_ERROR = /^(\d{1,3}) ([A-Z][A-Z ]*?) - ([\s\S]*)$/;
107
+ const codeOfApiStatus = (status) => {
108
+ switch (status) {
109
+ case 0:
110
+ return UPLOAD_FAILURE_CODE.network;
111
+ case 400:
112
+ return UPLOAD_FAILURE_CODE.invalid;
113
+ case 404:
114
+ return UPLOAD_FAILURE_CODE.notFound;
115
+ case 406:
116
+ return UPLOAD_FAILURE_CODE.notAcceptable;
117
+ case 409:
118
+ return UPLOAD_FAILURE_CODE.conflict;
119
+ case 410:
120
+ return UPLOAD_FAILURE_CODE.expired;
121
+ case 413:
122
+ return UPLOAD_FAILURE_CODE.tooLarge;
123
+ case 415:
124
+ return UPLOAD_FAILURE_CODE.unsupported;
125
+ default:
126
+ return UPLOAD_FAILURE_CODE.unknown;
127
+ }
128
+ };
129
+ /** normalize an error thrown by the API adapter */
130
+ export const asApiFailure = (error) => {
131
+ const message = error instanceof Error ? error.message : String(error);
132
+ const match = API_ERROR.exec(message);
133
+ if (!match)
134
+ return { source: UPLOAD_FAILURE_SOURCE.api, code: UPLOAD_FAILURE_CODE.unknown, message };
135
+ const status = Number(match[1]);
136
+ return {
137
+ source: UPLOAD_FAILURE_SOURCE.api,
138
+ code: codeOfApiStatus(status),
139
+ status,
140
+ reason: match[2],
141
+ message: match[3],
142
+ };
143
+ };
144
+ /** S3 error `<Code>` -> normalized code (roadmap 2). unknown codes fall back by status */
145
+ const STORAGE_CODES = {
146
+ SignatureDoesNotMatch: UPLOAD_FAILURE_CODE.signature,
147
+ AccessDenied: UPLOAD_FAILURE_CODE.expired,
148
+ ExpiredToken: UPLOAD_FAILURE_CODE.expired,
149
+ BadDigest: UPLOAD_FAILURE_CODE.checksum,
150
+ XAmzContentSHA256Mismatch: UPLOAD_FAILURE_CODE.checksum,
151
+ EntityTooLarge: UPLOAD_FAILURE_CODE.tooLarge,
152
+ RequestTimeout: UPLOAD_FAILURE_CODE.network,
153
+ };
154
+ /** normalize a non-2xx answer from the storage endpoint (status 0 = no response) */
155
+ export const asStorageFailure = (status, code) => {
156
+ const known = code ? STORAGE_CODES[code] : undefined;
157
+ const byStatus = status === 0
158
+ ? UPLOAD_FAILURE_CODE.network
159
+ : status === 403
160
+ ? UPLOAD_FAILURE_CODE.signature
161
+ : status === 413
162
+ ? UPLOAD_FAILURE_CODE.tooLarge
163
+ : UPLOAD_FAILURE_CODE.unknown;
164
+ return { source: UPLOAD_FAILURE_SOURCE.storage, code: known !== null && known !== void 0 ? known : byStatus, status, reason: code };
165
+ };
166
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../../src/upload/engine/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { UPLOAD_FAILURE_CODE, UPLOAD_FAILURE_SOURCE, UPLOAD_TRANSFER_KIND } from '../types.js';
11
+ import { asApiFailure, asStorageFailure } from './engine.js';
12
+ import { asContentBytes } from './progress.js';
13
+ /** roadmap 1 — the only place that knows bytes go to our API as base64 */
14
+ export class InlineExecutor {
15
+ constructor(send,
16
+ /** shell-specific base64 encoder (btoa / Buffer / RN polyfill) */
17
+ toBase64) {
18
+ this.send = send;
19
+ this.toBase64 = toBase64;
20
+ this.kind = UPLOAD_TRANSFER_KIND.inline;
21
+ }
22
+ run(id, transfer, source, onProgress) {
23
+ return __awaiter(this, void 0, void 0, function* () {
24
+ if (source.contentSize > transfer.maxBytes) {
25
+ return { id, failure: { source: UPLOAD_FAILURE_SOURCE.client, code: UPLOAD_FAILURE_CODE.tooLarge } };
26
+ }
27
+ try {
28
+ const body = { content: this.toBase64(yield source.bytes()) };
29
+ // wire bytes are base64 JSON (~4/3 of the send): report in send bytes
30
+ yield this.send(id, body, (loaded, total) => onProgress(asContentBytes(loaded, total, source.contentSize)));
31
+ return { id };
32
+ }
33
+ catch (error) {
34
+ return { id, failure: asApiFailure(error) };
35
+ }
36
+ });
37
+ }
38
+ }
39
+ /** roadmap 2 — added next to `InlineExecutor`; `UploadEngine` is untouched */
40
+ export class PresignedPutExecutor {
41
+ constructor(put) {
42
+ this.put = put;
43
+ this.kind = UPLOAD_TRANSFER_KIND.presignedPut;
44
+ }
45
+ run(id, transfer, source, onProgress) {
46
+ return __awaiter(this, void 0, void 0, function* () {
47
+ const res = yield this.put(transfer.url, transfer.headers, yield source.bytes(), (loaded, total) => onProgress(asContentBytes(loaded, total, source.contentSize)));
48
+ if (res.status >= 200 && res.status < 300)
49
+ return { id };
50
+ // 412 = object already exists under this key; the server decides at complete
51
+ if (res.status === 412)
52
+ return { id };
53
+ return { id, failure: asStorageFailure(res.status, res.code) };
54
+ });
55
+ }
56
+ }
57
+ //# sourceMappingURL=executors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executors.js","sourceRoot":"","sources":["../../../../src/upload/engine/executors.ts"],"names":[],"mappings":";;;;;;;;;AAgBA,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAE5F,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE1D,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAS5C,0EAA0E;AAC1E,MAAM,OAAO,cAAc;IAGvB,YACqB,IAAoB;IACrC,kEAAkE;IACjD,QAAuC;QAFvC,SAAI,GAAJ,IAAI,CAAgB;QAEpB,aAAQ,GAAR,QAAQ,CAA+B;QAL5C,SAAI,GAAG,oBAAoB,CAAC,MAAM,CAAC;IAMhD,CAAC;IAES,GAAG,CACZ,EAAU,EACV,QAA8B,EAC9B,MAAoB,EACpB,UAA8B;;YAE9B,IAAI,MAAM,CAAC,WAAW,GAAG,QAAQ,CAAC,QAAQ,EAAE;gBACxC,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,qBAAqB,CAAC,MAAM,EAAE,IAAI,EAAE,mBAAmB,CAAC,QAAQ,EAAE,EAAE,CAAC;aACxG;YACD,IAAI;gBACA,MAAM,IAAI,GAAmB,EAAE,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC;gBAC9E,sEAAsE;gBACtE,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;gBAC5G,OAAO,EAAE,EAAE,EAAE,CAAC;aACjB;YAAC,OAAO,KAAK,EAAE;gBACZ,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;aAC/C;QACL,CAAC;KAAA;CACJ;AAaD,8EAA8E;AAC9E,MAAM,OAAO,oBAAoB;IAG7B,YAAoC,GAAW;QAAX,QAAG,GAAH,GAAG,CAAQ;QAF/B,SAAI,GAAG,oBAAoB,CAAC,YAAY,CAAC;IAEP,CAAC;IAEtC,GAAG,CACZ,EAAU,EACV,QAAoC,EACpC,MAAoB,EACpB,UAA8B;;YAE9B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAC/F,UAAU,CAAC,cAAc,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAChE,CAAC;YACF,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG;gBAAE,OAAO,EAAE,EAAE,EAAE,CAAC;YACzD,6EAA6E;YAC7E,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;gBAAE,OAAO,EAAE,EAAE,EAAE,CAAC;YACtC,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACnE,CAAC;KAAA;CACJ"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `upload/engine/index.ts`
3
+ * - isolated entry for the client upload engine: `import { ... } from 'lemon-model/upload/engine'`.
4
+ * - kept OUT of both the package root barrel and `lemon-model/upload`: a server consumer needs the
5
+ * contract only, and must not pull the engine in with it.
6
+ * - shell adapters (XHR, RN bridge, Electron fs) are NOT here — they belong to the consuming app.
7
+ *
8
+ * @copyright (C) 2026 LemonCloud Co Ltd. - All Rights Reserved.
9
+ */
10
+ export * from './progress.js';
11
+ export * from './engine.js';
12
+ export * from './executors.js';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/upload/engine/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * `upload/engine/progress.ts`
3
+ * - progress is a CLIENT-LOCAL signal: nothing here travels on the wire and no server ever sees it.
4
+ * the server's knowledge of progress differs per transfer kind (a presigned PUT it cannot see at all),
5
+ * so the contract deliberately carries none of it.
6
+ * - scope A: per-file percent + one batch percent. everything is measured in bytes of the ORIGINAL
7
+ * content, never in wire bytes, so the bar means the same thing whichever executor runs.
8
+ *
9
+ * @copyright (C) 2026 LemonCloud Co Ltd. - All Rights Reserved.
10
+ */
11
+ /** scale request-body progress (base64 JSON or raw bytes) to original-send bytes */
12
+ export const asContentBytes = (loaded, total, contentSize) => total > 0 ? Math.min(contentSize, Math.round((loaded / total) * contentSize)) : 0;
13
+ /** byte-weighted aggregation; one immutable snapshot per change so a UI store can replace state as-is */
14
+ export class UploadProgressTracker {
15
+ constructor(totals, emit) {
16
+ this.emit = emit;
17
+ this.files = totals.map((total, index) => ({ index, total: Math.max(0, total), settled: false }));
18
+ this.totalBytes = this.files.reduce((sum, file) => sum + file.total, 0);
19
+ }
20
+ /** the per-file sink handed to an executor: clamps to [previous, total], ignores reports after settle */
21
+ sink(index) {
22
+ return sent => {
23
+ var _a;
24
+ const file = this.files[index];
25
+ if (!file || file.settled)
26
+ return;
27
+ const next = Math.min(file.total, Math.max(sent, (_a = file.sent) !== null && _a !== void 0 ? _a : 0));
28
+ if (next === file.sent)
29
+ return;
30
+ this.files = this.files.map(item => (item.index === index ? Object.assign(Object.assign({}, item), { sent: next }) : item));
31
+ this.publish();
32
+ };
33
+ }
34
+ settle(index) {
35
+ const file = this.files[index];
36
+ if (!file || file.settled)
37
+ return;
38
+ this.files = this.files.map(item => (item.index === index ? Object.assign(Object.assign({}, item), { settled: true }) : item));
39
+ this.publish();
40
+ }
41
+ snapshot() {
42
+ const sentBytes = this.files.reduce((sum, file) => { var _a; return sum + (file.settled ? file.total : (_a = file.sent) !== null && _a !== void 0 ? _a : 0); }, 0);
43
+ const settled = this.files.filter(file => file.settled).length;
44
+ const ratio = this.totalBytes > 0 ? sentBytes / this.totalBytes : this.files.length > 0 ? settled / this.files.length : 1;
45
+ return { files: this.files, totalBytes: this.totalBytes, sentBytes, ratio };
46
+ }
47
+ publish() {
48
+ if (this.emit)
49
+ this.emit(this.snapshot());
50
+ }
51
+ }
52
+ //# sourceMappingURL=progress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress.js","sourceRoot":"","sources":["../../../../src/upload/engine/progress.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `upload/index.ts`
3
+ * - isolated entry for the upload contract: `import { ... } from 'lemon-model/upload'`.
4
+ * - kept OUT of the package root barrel: a consumer that only needs this contract must not pull
5
+ * the socket/genai runtime, and a subpath can be promoted to the root later without a breaking
6
+ * change (the reverse is not true).
7
+ *
8
+ * @copyright (C) 2026 LemonCloud Co Ltd. - All Rights Reserved.
9
+ */
10
+ export * from './types.js';
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/upload/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,cAAc,SAAS,CAAC"}
@@ -0,0 +1,73 @@
1
+ import { UPLOAD_STATUS, UPLOAD_STEREO, UPLOAD_TRANSFER_KIND } from './types.js';
2
+ /** a settled image as the server returns it after `complete` */
3
+ export const SAMPLE_UPLOAD_STORED = {
4
+ id: 'up-001',
5
+ status: UPLOAD_STATUS.stored,
6
+ stereo: UPLOAD_STEREO.image,
7
+ name: 'photo.png',
8
+ contentType: 'image/png',
9
+ contentSize: 123456,
10
+ url: 'https://cdn.example.com/up-001.png',
11
+ width: 1024,
12
+ height: 768,
13
+ createdAt: 1757894400000,
14
+ updatedAt: 1757894401000,
15
+ };
16
+ /** the head a message embeds under `upload$$` for the same upload */
17
+ export const SAMPLE_UPLOAD_HEAD = {
18
+ id: 'up-001',
19
+ stereo: UPLOAD_STEREO.image,
20
+ name: 'photo.png',
21
+ contentType: 'image/png',
22
+ contentSize: 123456,
23
+ url: 'https://cdn.example.com/up-001.png',
24
+ width: 1024,
25
+ height: 768,
26
+ };
27
+ /** what a message view carries: both halves of the pair, never one */
28
+ export const SAMPLE_UPLOAD_REFS = {
29
+ uploadIds: ['up-001'],
30
+ upload$$: [SAMPLE_UPLOAD_HEAD],
31
+ };
32
+ /** start slot: inline ticket (roadmap 1) */
33
+ export const SAMPLE_UPLOAD_TICKET_INLINE = {
34
+ upload: {
35
+ id: 'up-002',
36
+ status: UPLOAD_STATUS.pending,
37
+ stereo: UPLOAD_STEREO.image,
38
+ name: 'photo.png',
39
+ contentType: 'image/png',
40
+ contentSize: 123456,
41
+ },
42
+ transfer: { kind: UPLOAD_TRANSFER_KIND.inline, maxBytes: 4000000 },
43
+ };
44
+ /** start slot: presigned ticket (roadmap 2) */
45
+ export const SAMPLE_UPLOAD_TICKET_PRESIGNED = {
46
+ upload: {
47
+ id: 'up-003',
48
+ status: UPLOAD_STATUS.pending,
49
+ stereo: UPLOAD_STEREO.image,
50
+ name: 'photo.png',
51
+ contentType: 'image/png',
52
+ contentSize: 123456,
53
+ },
54
+ transfer: {
55
+ kind: UPLOAD_TRANSFER_KIND.presignedPut,
56
+ method: 'PUT',
57
+ url: 'https://storage.example.com/up-003?X-Amz-Signature=masked',
58
+ headers: { 'content-type': 'image/png', 'content-length': '123456' },
59
+ maxBytes: 50000000,
60
+ expiresAt: 1757895300000,
61
+ },
62
+ };
63
+ /** start slot rejected at validation: no id, nothing to transfer or complete */
64
+ export const SAMPLE_UPLOAD_REJECTED = {
65
+ upload: {
66
+ status: UPLOAD_STATUS.failed,
67
+ name: 'big.png',
68
+ contentType: 'image/png',
69
+ contentSize: 6291456,
70
+ error: '413 TOO LARGE - 6291456 > 4000000',
71
+ },
72
+ };
73
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.js","sourceRoot":"","sources":["../../../src/upload/testing.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE7E,gEAAgE;AAChE,MAAM,CAAC,MAAM,oBAAoB,GAAe;IAC5C,EAAE,EAAE,QAAQ;IACZ,MAAM,EAAE,aAAa,CAAC,MAAM;IAC5B,MAAM,EAAE,aAAa,CAAC,KAAK;IAC3B,IAAI,EAAE,WAAW;IACjB,WAAW,EAAE,WAAW;IACxB,WAAW,EAAE,MAAM;IACnB,GAAG,EAAE,oCAAoC;IACzC,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,GAAG;IACX,SAAS,EAAE,aAAa;IACxB,SAAS,EAAE,aAAa;CAC3B,CAAC;AAEF,qEAAqE;AACrE,MAAM,CAAC,MAAM,kBAAkB,GAAe;IAC1C,EAAE,EAAE,QAAQ;IACZ,MAAM,EAAE,aAAa,CAAC,KAAK;IAC3B,IAAI,EAAE,WAAW;IACjB,WAAW,EAAE,WAAW;IACxB,WAAW,EAAE,MAAM;IACnB,GAAG,EAAE,oCAAoC;IACzC,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,GAAG;CACd,CAAC;AAEF,sEAAsE;AACtE,MAAM,CAAC,MAAM,kBAAkB,GAAe;IAC1C,SAAS,EAAE,CAAC,QAAQ,CAAC;IACrB,QAAQ,EAAE,CAAC,kBAAkB,CAAC;CACjC,CAAC;AAEF,4CAA4C;AAC5C,MAAM,CAAC,MAAM,2BAA2B,GAAiB;IACrD,MAAM,EAAE;QACJ,EAAE,EAAE,QAAQ;QACZ,MAAM,EAAE,aAAa,CAAC,OAAO;QAC7B,MAAM,EAAE,aAAa,CAAC,KAAK;QAC3B,IAAI,EAAE,WAAW;QACjB,WAAW,EAAE,WAAW;QACxB,WAAW,EAAE,MAAM;KACtB;IACD,QAAQ,EAAE,EAAE,IAAI,EAAE,oBAAoB,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE;CACrE,CAAC;AAEF,+CAA+C;AAC/C,MAAM,CAAC,MAAM,8BAA8B,GAAiB;IACxD,MAAM,EAAE;QACJ,EAAE,EAAE,QAAQ;QACZ,MAAM,EAAE,aAAa,CAAC,OAAO;QAC7B,MAAM,EAAE,aAAa,CAAC,KAAK;QAC3B,IAAI,EAAE,WAAW;QACjB,WAAW,EAAE,WAAW;QACxB,WAAW,EAAE,MAAM;KACtB;IACD,QAAQ,EAAE;QACN,IAAI,EAAE,oBAAoB,CAAC,YAAY;QACvC,MAAM,EAAE,KAAK;QACb,GAAG,EAAE,2DAA2D;QAChE,OAAO,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,gBAAgB,EAAE,QAAQ,EAAE;QACpE,QAAQ,EAAE,QAAQ;QAClB,SAAS,EAAE,aAAa;KAC3B;CACJ,CAAC;AAEF,gFAAgF;AAChF,MAAM,CAAC,MAAM,sBAAsB,GAAiB;IAChD,MAAM,EAAE;QACJ,MAAM,EAAE,aAAa,CAAC,MAAM;QAC5B,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,WAAW;QACxB,WAAW,EAAE,OAAO;QACpB,KAAK,EAAE,mCAAmC;KAC7C;CACJ,CAAC"}
@@ -0,0 +1,69 @@
1
+ /** kind of stored content. same vocabulary as lemon-uploads-api `MediaStereo` minus its internal markers */
2
+ export const UPLOAD_STEREO = {
3
+ image: 'image',
4
+ video: 'video',
5
+ sound: 'sound',
6
+ docs: 'docs',
7
+ };
8
+ /** lifecycle of one upload as the client sees it. `url` is meaningful only when `stored` */
9
+ export const UPLOAD_STATUS = {
10
+ /** ticket issued; bytes not confirmed by the server yet */
11
+ pending: 'pending',
12
+ /** bytes durable and `url` valid */
13
+ stored: 'stored',
14
+ /** terminal; `error` carries "<status> <LABEL> - <detail>" */
15
+ failed: 'failed',
16
+ };
17
+ /** how bytes travel. the only axis that changes across the roadmap */
18
+ export const UPLOAD_TRANSFER_KIND = {
19
+ /** base64 JSON to the API via the `send` operation (roadmap 1) */
20
+ inline: 'inline',
21
+ /** HTTP PUT straight to storage with a server-issued url + headers (roadmap 2) */
22
+ presignedPut: 'presigned-put',
23
+ };
24
+ /** who produced a failure */
25
+ export const UPLOAD_FAILURE_SOURCE = {
26
+ /** our API answered with an error */
27
+ api: 'api',
28
+ /** the storage endpoint (presigned PUT) answered with an error */
29
+ storage: 'storage',
30
+ /** the client gave up before or without a response */
31
+ client: 'client',
32
+ };
33
+ /** normalized failure codes; the app branches on these, never on raw HTTP status */
34
+ export const UPLOAD_FAILURE_CODE = {
35
+ invalid: 'invalid',
36
+ tooLarge: 'too-large',
37
+ unsupported: 'unsupported',
38
+ notAcceptable: 'not-acceptable',
39
+ notFound: 'not-found',
40
+ conflict: 'conflict',
41
+ expired: 'expired',
42
+ signature: 'signature',
43
+ checksum: 'checksum',
44
+ network: 'network',
45
+ aborted: 'aborted',
46
+ unknown: 'unknown',
47
+ };
48
+ /** HTTP binding relative to the server's upload base path (lemon-core `/{type}/{id}/{cmd}` shape) */
49
+ export const UPLOAD_ROUTES = {
50
+ start: { method: 'POST', path: '/start' },
51
+ send: { method: 'POST', path: '/{id}/send' },
52
+ complete: { method: 'POST', path: '/complete' },
53
+ read: { method: 'GET', path: '/{id}' },
54
+ };
55
+ export const isUploadStored = (view) => view.status === UPLOAD_STATUS.stored && typeof view.id === 'string' && typeof view.url === 'string';
56
+ /** shared stereo derivation so an optimistic client tile and the server agree */
57
+ export const uploadStereoOf = (contentType) => {
58
+ const type = contentType.toLowerCase().split(';')[0].trim();
59
+ if (type.startsWith('image/'))
60
+ return UPLOAD_STEREO.image;
61
+ if (type.startsWith('video/'))
62
+ return UPLOAD_STEREO.video;
63
+ if (type.startsWith('audio/'))
64
+ return UPLOAD_STEREO.sound;
65
+ if (type === 'application/pdf')
66
+ return UPLOAD_STEREO.docs;
67
+ return undefined;
68
+ };
69
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/upload/types.ts"],"names":[],"mappings":"AAaA,4GAA4G;AAC5G,MAAM,CAAC,MAAM,aAAa,GAAG;IACzB,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;CACN,CAAC;AAGX,4FAA4F;AAC5F,MAAM,CAAC,MAAM,aAAa,GAAG;IACzB,2DAA2D;IAC3D,OAAO,EAAE,SAAS;IAClB,oCAAoC;IACpC,MAAM,EAAE,QAAQ;IAChB,8DAA8D;IAC9D,MAAM,EAAE,QAAQ;CACV,CAAC;AAGX,sEAAsE;AACtE,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAChC,kEAAkE;IAClE,MAAM,EAAE,QAAQ;IAChB,kFAAkF;IAClF,YAAY,EAAE,eAAe;CACvB,CAAC;AAoHX,6BAA6B;AAC7B,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACjC,qCAAqC;IACrC,GAAG,EAAE,KAAK;IACV,kEAAkE;IAClE,OAAO,EAAE,SAAS;IAClB,sDAAsD;IACtD,MAAM,EAAE,QAAQ;CACV,CAAC;AAGX,oFAAoF;AACpF,MAAM,CAAC,MAAM,mBAAmB,GAAG;IAC/B,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,WAAW;IACrB,WAAW,EAAE,aAAa;IAC1B,aAAa,EAAE,gBAAgB;IAC/B,QAAQ,EAAE,WAAW;IACrB,QAAQ,EAAE,UAAU;IACpB,OAAO,EAAE,SAAS;IAClB,SAAS,EAAE,WAAW;IACtB,QAAQ,EAAE,UAAU;IACpB,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;CACZ,CAAC;AA8CX,qGAAqG;AACrG,MAAM,CAAC,MAAM,aAAa,GAAG;IACzB,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE;IACzC,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE;IAC5C,QAAQ,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE;IAC/C,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE;CAChC,CAAC;AAKX,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,IAAgB,EAA4B,EAAE,CACzE,IAAI,CAAC,MAAM,KAAK,aAAa,CAAC,MAAM,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ,CAAC;AAExG,iFAAiF;AACjF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,WAAmB,EAA4B,EAAE;IAC5E,MAAM,IAAI,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5D,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,aAAa,CAAC,KAAK,CAAC;IAC1D,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,aAAa,CAAC,KAAK,CAAC;IAC1D,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,aAAa,CAAC,KAAK,CAAC;IAC1D,IAAI,IAAI,KAAK,iBAAiB;QAAE,OAAO,aAAa,CAAC,IAAI,CAAC;IAC1D,OAAO,SAAS,CAAC;AACrB,CAAC,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * `upload/engine/engine.ts`
3
+ * - the one upload flow every shell shares: `start` -> run executors -> `complete`.
4
+ * - swapping the server's transfer method never touches this file; a new `UploadTransfer` kind
5
+ * arrives as one more `UploadTransferExecutor` passed to the constructor.
6
+ * - runtime-neutral like the contract itself: no Node, DOM, or AWS types. the shell injects the
7
+ * three things that are shell-specific — reading bytes (`UploadSource`), the HTTP primitives
8
+ * (see `./executors`), and hashing.
9
+ *
10
+ * @copyright (C) 2026 LemonCloud Co Ltd. - All Rights Reserved.
11
+ */
12
+ import type { UploadCompleteItem, UploadFailure, UploadService, UploadTransfer, UploadView } from '../types';
13
+ import type { UploadBatchProgressSink, UploadProgressSink } from './progress';
14
+ /** bytes provider; hides DOM `File`, RN uri, Electron path behind one shape */
15
+ export interface UploadSource {
16
+ name: string;
17
+ contentType: string;
18
+ contentSize: number;
19
+ /** whole send (roadmap 1·2 sizes) */
20
+ bytes(): Promise<Uint8Array>;
21
+ /** sha256 hex(64) when this shell chooses to hash at send time; omit to skip */
22
+ hash?(): Promise<string | undefined>;
23
+ }
24
+ /** moves bytes for one transfer kind and reports the receipt the server expects in `complete` */
25
+ export interface UploadTransferExecutor<T extends UploadTransfer = UploadTransfer> {
26
+ readonly kind: T['kind'];
27
+ /** `onProgress` is optional to CALL, not to accept: an executor that cannot observe bytes never calls it */
28
+ run(id: string, transfer: T, source: UploadSource, onProgress: UploadProgressSink): Promise<UploadCompleteItem>;
29
+ }
30
+ /**
31
+ * transfers in flight at once.
32
+ * - a browser queues beyond ~6 per origin, and a queued presigned url burns its `expiresAt` while it waits.
33
+ * - override per engine when a shell measures something better.
34
+ */
35
+ export declare const UPLOAD_DEFAULT_CONCURRENCY = 3;
36
+ export declare class UploadEngine {
37
+ private readonly service;
38
+ private readonly executors;
39
+ private readonly options?;
40
+ private readonly kinds;
41
+ constructor(service: UploadService, executors: ReadonlyArray<UploadTransferExecutor>, options?: {
42
+ concurrency?: number;
43
+ });
44
+ /** returns one view per source, in order: `stored`, or `failed` with the reason. `onProgress` gets a snapshot per change */
45
+ upload(sources: ReadonlyArray<UploadSource>, onProgress?: UploadBatchProgressSink): Promise<UploadView[]>;
46
+ private transferTracked;
47
+ private transfer;
48
+ }
49
+ /** normalize an error thrown by the API adapter */
50
+ export declare const asApiFailure: (error: unknown) => UploadFailure;
51
+ /** normalize a non-2xx answer from the storage endpoint (status 0 = no response) */
52
+ export declare const asStorageFailure: (status: number, code?: string) => UploadFailure;