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,230 @@
1
+ /**
2
+ * `upload/types.ts`
3
+ * - client <-> server contract for uploading files.
4
+ * - transport-neutral: the client always runs `start` -> transfer -> `complete`; whether the server
5
+ * stores bytes inline (base64 in JSON) or hands out a presigned PUT only changes which
6
+ * `UploadTransfer` variant it issues.
7
+ * - runtime-neutral like `socket/`: no Node, DOM, or AWS SDK types. Storage coordinates
8
+ * (bucket, key, region) never appear here.
9
+ *
10
+ * @copyright (C) 2026 LemonCloud Co Ltd. - All Rights Reserved.
11
+ */
12
+ import type { Body, View } from '../cores/transformer';
13
+ /** kind of stored content. same vocabulary as lemon-uploads-api `MediaStereo` minus its internal markers */
14
+ export declare const UPLOAD_STEREO: {
15
+ readonly image: "image";
16
+ readonly video: "video";
17
+ readonly sound: "sound";
18
+ readonly docs: "docs";
19
+ };
20
+ export declare type UploadStereo = typeof UPLOAD_STEREO[keyof typeof UPLOAD_STEREO];
21
+ /** lifecycle of one upload as the client sees it. `url` is meaningful only when `stored` */
22
+ export declare const UPLOAD_STATUS: {
23
+ /** ticket issued; bytes not confirmed by the server yet */
24
+ readonly pending: "pending";
25
+ /** bytes durable and `url` valid */
26
+ readonly stored: "stored";
27
+ /** terminal; `error` carries "<status> <LABEL> - <detail>" */
28
+ readonly failed: "failed";
29
+ };
30
+ export declare type UploadStatus = typeof UPLOAD_STATUS[keyof typeof UPLOAD_STATUS];
31
+ /** how bytes travel. the only axis that changes across the roadmap */
32
+ export declare const UPLOAD_TRANSFER_KIND: {
33
+ /** base64 JSON to the API via the `send` operation (roadmap 1) */
34
+ readonly inline: "inline";
35
+ /** HTTP PUT straight to storage with a server-issued url + headers (roadmap 2) */
36
+ readonly presignedPut: "presigned-put";
37
+ };
38
+ export declare type UploadTransferKind = typeof UPLOAD_TRANSFER_KIND[keyof typeof UPLOAD_TRANSFER_KIND];
39
+ /**
40
+ * snapshot other models embed as `upload$` / `upload$$` (see `UploadRef`, `UploadRefs`).
41
+ * - what a list screen needs to draw a tile or a file chip without a second fetch.
42
+ */
43
+ export interface UploadHead {
44
+ id?: string;
45
+ stereo?: UploadStereo;
46
+ /** file name as the sender picked it */
47
+ name?: string;
48
+ /** MIME type */
49
+ contentType?: string;
50
+ /** size in bytes of the original content */
51
+ contentSize?: number;
52
+ /** stable url to serve; present iff status is `stored` */
53
+ url?: string;
54
+ /** thumbnail url; may arrive after `stored` (server post-processing) */
55
+ thumbnail?: string;
56
+ /** pixel size for image/video */
57
+ width?: number;
58
+ height?: number;
59
+ }
60
+ /** response layout of one upload */
61
+ export interface UploadView extends View, UploadHead {
62
+ status: UploadStatus;
63
+ /** sha256 hex(64) of the content, when known to the server */
64
+ hash?: string;
65
+ }
66
+ /**
67
+ * request layout of one upload intent.
68
+ * - deviates from `XBody extends Body, Partial<XView>`: the three fields are required because
69
+ * the server picks the transfer and validates limits from them before any byte moves.
70
+ * - inherited `id` set = re-issue the transfer of a still-`pending` upload (expired ticket).
71
+ */
72
+ export interface UploadBody extends Body {
73
+ name: string;
74
+ contentType: string;
75
+ contentSize: number;
76
+ /** sha256 hex(64). optional forever: servers MUST accept uploads without it, MUST verify it when given */
77
+ hash?: string;
78
+ }
79
+ /** single reference pair. add both fields or neither */
80
+ export interface UploadRef {
81
+ uploadId?: string;
82
+ upload$?: UploadHead;
83
+ }
84
+ /** array reference pair. add both fields or neither */
85
+ export interface UploadRefs {
86
+ uploadIds?: string[];
87
+ upload$$?: UploadHead[];
88
+ }
89
+ /** transfer instruction (roadmap 1): send base64 through `UploadService.send()` */
90
+ export interface UploadInlineTransfer {
91
+ kind: typeof UPLOAD_TRANSFER_KIND.inline;
92
+ /** max original bytes (before base64) this server accepts inline */
93
+ maxBytes: number;
94
+ }
95
+ /**
96
+ * transfer instruction (roadmap 2): PUT the raw bytes to `url` with `headers` verbatim.
97
+ * - `url` is a credential. never log, persist, or put it in an error message.
98
+ * - `expiresAt` is an upper bound only (signing credentials may expire earlier). on 403 re-`start` with `id`.
99
+ */
100
+ export interface UploadPresignedPutTransfer {
101
+ kind: typeof UPLOAD_TRANSFER_KIND.presignedPut;
102
+ method: 'PUT';
103
+ url: string;
104
+ /**
105
+ * every header the signature covers; send them unchanged.
106
+ * - EXCEPT the ones the user agent owns: `content-length` and `host`. `fetch` and
107
+ * `XMLHttpRequest` forbid setting them and the UA fills them from the request itself,
108
+ * so a shell adapter must drop them from this map before sending. The signature still
109
+ * matches because the UA's value is the one that was signed. `[실측]`
110
+ */
111
+ headers: Record<string, string>;
112
+ maxBytes: number;
113
+ /** epoch-ms */
114
+ expiresAt?: number;
115
+ }
116
+ export declare type UploadTransfer = UploadInlineTransfer | UploadPresignedPutTransfer;
117
+ /**
118
+ * one slot of `start`'s answer.
119
+ * - `upload` is safe to store and log. `transfer` is consumed once by the executor and dropped.
120
+ * - no `transfer` = nothing to send: already `stored` (dedup) or `failed` at validation.
121
+ */
122
+ export interface UploadTicket {
123
+ upload: UploadView;
124
+ transfer?: UploadTransfer;
125
+ }
126
+ export interface UploadStartBody {
127
+ list: UploadBody[];
128
+ /** transfer kinds this client can execute, preferred first. absent = `['inline']` */
129
+ transfers?: UploadTransferKind[];
130
+ }
131
+ /** same length and order as `UploadStartBody.list` */
132
+ export interface UploadStartResult {
133
+ list: UploadTicket[];
134
+ }
135
+ /** body of the `send` operation (inline transfer only) */
136
+ export interface UploadSendBody {
137
+ /** base64 of the whole content; size must match the declared `contentSize` */
138
+ content: string;
139
+ }
140
+ /** who produced a failure */
141
+ export declare const UPLOAD_FAILURE_SOURCE: {
142
+ /** our API answered with an error */
143
+ readonly api: "api";
144
+ /** the storage endpoint (presigned PUT) answered with an error */
145
+ readonly storage: "storage";
146
+ /** the client gave up before or without a response */
147
+ readonly client: "client";
148
+ };
149
+ export declare type UploadFailureSource = typeof UPLOAD_FAILURE_SOURCE[keyof typeof UPLOAD_FAILURE_SOURCE];
150
+ /** normalized failure codes; the app branches on these, never on raw HTTP status */
151
+ export declare const UPLOAD_FAILURE_CODE: {
152
+ readonly invalid: "invalid";
153
+ readonly tooLarge: "too-large";
154
+ readonly unsupported: "unsupported";
155
+ readonly notAcceptable: "not-acceptable";
156
+ readonly notFound: "not-found";
157
+ readonly conflict: "conflict";
158
+ readonly expired: "expired";
159
+ readonly signature: "signature";
160
+ readonly checksum: "checksum";
161
+ readonly network: "network";
162
+ readonly aborted: "aborted";
163
+ readonly unknown: "unknown";
164
+ };
165
+ export declare type UploadFailureCode = typeof UPLOAD_FAILURE_CODE[keyof typeof UPLOAD_FAILURE_CODE];
166
+ /** failure as the client reports it (to the app, and to the server in `complete`) */
167
+ export interface UploadFailure {
168
+ source: UploadFailureSource;
169
+ code: UploadFailureCode;
170
+ /** HTTP status as observed, if any */
171
+ status?: number;
172
+ /** origin code: S3 `<Code>` or the lemon-core LABEL */
173
+ reason?: string;
174
+ /** human detail. MUST NOT contain a transfer url */
175
+ message?: string;
176
+ }
177
+ /** one slot of `complete`'s request */
178
+ export interface UploadCompleteItem {
179
+ id: string;
180
+ /** set when the transfer did not succeed; the server marks the upload `failed` */
181
+ failure?: UploadFailure;
182
+ }
183
+ export interface UploadCompleteBody {
184
+ list: UploadCompleteItem[];
185
+ }
186
+ /** same length and order as `UploadCompleteBody.list` */
187
+ export interface UploadCompleteResult {
188
+ list: UploadView[];
189
+ }
190
+ /**
191
+ * the four operations. the server controller and the client API adapter both implement this.
192
+ * - all four require the caller's normal API auth; a presigned url is the only unauthenticated hop.
193
+ */
194
+ export interface UploadService {
195
+ /** declare intents; get tickets. validation failures come back per slot as `failed`, never as HTTP 4xx */
196
+ start(body: UploadStartBody): Promise<UploadStartResult>;
197
+ /** inline transfer: deliver the bytes of one pending upload */
198
+ send(id: string, body: UploadSendBody): Promise<UploadView>;
199
+ /** confirm; the server verifies what it cannot see (presigned) and settles `status`. idempotent */
200
+ complete(body: UploadCompleteBody): Promise<UploadCompleteResult>;
201
+ /** re-read (post-processing fields may arrive later) */
202
+ read(id: string): Promise<UploadView>;
203
+ }
204
+ /** HTTP binding relative to the server's upload base path (lemon-core `/{type}/{id}/{cmd}` shape) */
205
+ export declare const UPLOAD_ROUTES: {
206
+ readonly start: {
207
+ readonly method: "POST";
208
+ readonly path: "/start";
209
+ };
210
+ readonly send: {
211
+ readonly method: "POST";
212
+ readonly path: "/{id}/send";
213
+ };
214
+ readonly complete: {
215
+ readonly method: "POST";
216
+ readonly path: "/complete";
217
+ };
218
+ readonly read: {
219
+ readonly method: "GET";
220
+ readonly path: "/{id}";
221
+ };
222
+ };
223
+ /** a view whose bytes are durable: `id` and `url` are guaranteed */
224
+ export declare type UploadStoredView = UploadView & {
225
+ id: string;
226
+ url: string;
227
+ };
228
+ export declare const isUploadStored: (view: UploadView) => view is UploadStoredView;
229
+ /** shared stereo derivation so an optimistic client tile and the server agree */
230
+ export declare const uploadStereoOf: (contentType: string) => UploadStereo | undefined;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.uploadStereoOf = exports.isUploadStored = exports.UPLOAD_ROUTES = exports.UPLOAD_FAILURE_CODE = exports.UPLOAD_FAILURE_SOURCE = exports.UPLOAD_TRANSFER_KIND = exports.UPLOAD_STATUS = exports.UPLOAD_STEREO = void 0;
4
+ /** kind of stored content. same vocabulary as lemon-uploads-api `MediaStereo` minus its internal markers */
5
+ exports.UPLOAD_STEREO = {
6
+ image: 'image',
7
+ video: 'video',
8
+ sound: 'sound',
9
+ docs: 'docs',
10
+ };
11
+ /** lifecycle of one upload as the client sees it. `url` is meaningful only when `stored` */
12
+ exports.UPLOAD_STATUS = {
13
+ /** ticket issued; bytes not confirmed by the server yet */
14
+ pending: 'pending',
15
+ /** bytes durable and `url` valid */
16
+ stored: 'stored',
17
+ /** terminal; `error` carries "<status> <LABEL> - <detail>" */
18
+ failed: 'failed',
19
+ };
20
+ /** how bytes travel. the only axis that changes across the roadmap */
21
+ exports.UPLOAD_TRANSFER_KIND = {
22
+ /** base64 JSON to the API via the `send` operation (roadmap 1) */
23
+ inline: 'inline',
24
+ /** HTTP PUT straight to storage with a server-issued url + headers (roadmap 2) */
25
+ presignedPut: 'presigned-put',
26
+ };
27
+ /** who produced a failure */
28
+ exports.UPLOAD_FAILURE_SOURCE = {
29
+ /** our API answered with an error */
30
+ api: 'api',
31
+ /** the storage endpoint (presigned PUT) answered with an error */
32
+ storage: 'storage',
33
+ /** the client gave up before or without a response */
34
+ client: 'client',
35
+ };
36
+ /** normalized failure codes; the app branches on these, never on raw HTTP status */
37
+ exports.UPLOAD_FAILURE_CODE = {
38
+ invalid: 'invalid',
39
+ tooLarge: 'too-large',
40
+ unsupported: 'unsupported',
41
+ notAcceptable: 'not-acceptable',
42
+ notFound: 'not-found',
43
+ conflict: 'conflict',
44
+ expired: 'expired',
45
+ signature: 'signature',
46
+ checksum: 'checksum',
47
+ network: 'network',
48
+ aborted: 'aborted',
49
+ unknown: 'unknown',
50
+ };
51
+ /** HTTP binding relative to the server's upload base path (lemon-core `/{type}/{id}/{cmd}` shape) */
52
+ exports.UPLOAD_ROUTES = {
53
+ start: { method: 'POST', path: '/start' },
54
+ send: { method: 'POST', path: '/{id}/send' },
55
+ complete: { method: 'POST', path: '/complete' },
56
+ read: { method: 'GET', path: '/{id}' },
57
+ };
58
+ const isUploadStored = (view) => view.status === exports.UPLOAD_STATUS.stored && typeof view.id === 'string' && typeof view.url === 'string';
59
+ exports.isUploadStored = isUploadStored;
60
+ /** shared stereo derivation so an optimistic client tile and the server agree */
61
+ const uploadStereoOf = (contentType) => {
62
+ const type = contentType.toLowerCase().split(';')[0].trim();
63
+ if (type.startsWith('image/'))
64
+ return exports.UPLOAD_STEREO.image;
65
+ if (type.startsWith('video/'))
66
+ return exports.UPLOAD_STEREO.video;
67
+ if (type.startsWith('audio/'))
68
+ return exports.UPLOAD_STEREO.sound;
69
+ if (type === 'application/pdf')
70
+ return exports.UPLOAD_STEREO.docs;
71
+ return undefined;
72
+ };
73
+ exports.uploadStereoOf = uploadStereoOf;
74
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/upload/types.ts"],"names":[],"mappings":";;;AAaA,4GAA4G;AAC/F,QAAA,aAAa,GAAG;IACzB,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;CACN,CAAC;AAGX,4FAA4F;AAC/E,QAAA,aAAa,GAAG;IACzB,2DAA2D;IAC3D,OAAO,EAAE,SAAS;IAClB,oCAAoC;IACpC,MAAM,EAAE,QAAQ;IAChB,8DAA8D;IAC9D,MAAM,EAAE,QAAQ;CACV,CAAC;AAGX,sEAAsE;AACzD,QAAA,oBAAoB,GAAG;IAChC,kEAAkE;IAClE,MAAM,EAAE,QAAQ;IAChB,kFAAkF;IAClF,YAAY,EAAE,eAAe;CACvB,CAAC;AAoHX,6BAA6B;AAChB,QAAA,qBAAqB,GAAG;IACjC,qCAAqC;IACrC,GAAG,EAAE,KAAK;IACV,kEAAkE;IAClE,OAAO,EAAE,SAAS;IAClB,sDAAsD;IACtD,MAAM,EAAE,QAAQ;CACV,CAAC;AAGX,oFAAoF;AACvE,QAAA,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;AACxF,QAAA,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;AAKJ,MAAM,cAAc,GAAG,CAAC,IAAgB,EAA4B,EAAE,CACzE,IAAI,CAAC,MAAM,KAAK,qBAAa,CAAC,MAAM,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ,CAAC;AAD3F,QAAA,cAAc,kBAC6E;AAExG,iFAAiF;AAC1E,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,qBAAa,CAAC,KAAK,CAAC;IAC1D,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,qBAAa,CAAC,KAAK,CAAC;IAC1D,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,qBAAa,CAAC,KAAK,CAAC;IAC1D,IAAI,IAAI,KAAK,iBAAiB;QAAE,OAAO,qBAAa,CAAC,IAAI,CAAC;IAC1D,OAAO,SAAS,CAAC;AACrB,CAAC,CAAC;AAPW,QAAA,cAAc,kBAOzB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lemon-model",
3
- "version": "1.2.4",
3
+ "version": "1.4.0",
4
4
  "description": "Common shared model definitions for Lemon Micro-Service Platform",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -42,8 +42,33 @@
42
42
  "require": "./dist/logtrace/testing.js",
43
43
  "default": "./dist/logtrace/testing.js"
44
44
  },
45
+ "./upload": {
46
+ "types": "./dist/upload/index.d.ts",
47
+ "import": "./dist/esm/upload/index.js",
48
+ "require": "./dist/upload/index.js",
49
+ "default": "./dist/upload/index.js"
50
+ },
51
+ "./upload/testing": {
52
+ "types": "./dist/upload/testing.d.ts",
53
+ "import": "./dist/esm/upload/testing.js",
54
+ "require": "./dist/upload/testing.js",
55
+ "default": "./dist/upload/testing.js"
56
+ },
57
+ "./upload/engine": {
58
+ "types": "./dist/upload/engine/index.d.ts",
59
+ "import": "./dist/esm/upload/engine/index.js",
60
+ "require": "./dist/upload/engine/index.js",
61
+ "default": "./dist/upload/engine/index.js"
62
+ },
45
63
  "./package.json": "./package.json"
46
64
  },
65
+ "typesVersions": {
66
+ "*": {
67
+ "upload": ["dist/upload/index.d.ts"],
68
+ "upload/testing": ["dist/upload/testing.d.ts"],
69
+ "upload/engine": ["dist/upload/engine/index.d.ts"]
70
+ }
71
+ },
47
72
  "scripts": {
48
73
  "build": "npm run build-ts && npm run build-esm",
49
74
  "build-ts": "tsc -v && tsc",