@openrails/contentkit-ui 0.79.0 → 0.81.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.
- package/README.md +82 -54
- package/dist/client/beacons.d.ts +53 -0
- package/dist/client/beacons.js +72 -0
- package/dist/client/beacons.js.map +1 -0
- package/dist/client/catalog.d.ts +2 -2
- package/dist/client/catalog.js +1 -1
- package/dist/client/catalog.js.map +1 -1
- package/dist/client/client.d.ts +24 -11
- package/dist/client/client.js +37 -29
- package/dist/client/client.js.map +1 -1
- package/dist/client/content/comments.d.ts +12 -8
- package/dist/client/content/comments.js +33 -20
- package/dist/client/content/comments.js.map +1 -1
- package/dist/client/content/favorites.d.ts +24 -9
- package/dist/client/content/favorites.js +21 -22
- package/dist/client/content/favorites.js.map +1 -1
- package/dist/client/content/moderation.d.ts +1 -1
- package/dist/client/content/moderation.js.map +1 -1
- package/dist/client/content/reactions.d.ts +11 -6
- package/dist/client/content/reactions.js +23 -18
- package/dist/client/content/reactions.js.map +1 -1
- package/dist/client/content/types.d.ts +40 -21
- package/dist/client/content/types.js +5 -2
- package/dist/client/content/types.js.map +1 -1
- package/dist/client/contents.d.ts +55 -0
- package/dist/client/contents.js +144 -0
- package/dist/client/contents.js.map +1 -0
- package/dist/client/discovery.d.ts +126 -0
- package/dist/client/discovery.js +125 -0
- package/dist/client/discovery.js.map +1 -0
- package/dist/client/generated/error-codes.d.ts +1 -1
- package/dist/client/generated/error-codes.js +12 -0
- package/dist/client/generated/error-codes.js.map +1 -1
- package/dist/client/generated/routes.d.ts +1244 -370
- package/dist/client/generated/routes.js +1870 -554
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +960 -134
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/group-fields.d.ts +25 -0
- package/dist/client/group-fields.js +63 -0
- package/dist/client/group-fields.js.map +1 -0
- package/dist/client/groups.d.ts +58 -0
- package/dist/client/groups.js +119 -0
- package/dist/client/groups.js.map +1 -0
- package/dist/client/http.d.ts +24 -12
- package/dist/client/http.js +20 -24
- package/dist/client/http.js.map +1 -1
- package/dist/client/index.d.ts +11 -6
- package/dist/client/media/api.d.ts +1 -1
- package/dist/client/media/api.js +4 -2
- package/dist/client/media/api.js.map +1 -1
- package/dist/client/media/client.d.ts +8 -9
- package/dist/client/media/client.js +58 -71
- package/dist/client/media/client.js.map +1 -1
- package/dist/client/media/ref.d.ts +30 -0
- package/dist/client/media/ref.js +10 -3
- package/dist/client/media/ref.js.map +1 -1
- package/dist/client/offers.d.ts +41 -0
- package/dist/client/offers.js +66 -0
- package/dist/client/offers.js.map +1 -0
- package/dist/client/route.d.ts +2 -0
- package/dist/client/route.js +2 -1
- package/dist/client/route.js.map +1 -1
- package/dist/client/versions.d.ts +20 -0
- package/dist/client/versions.js +53 -0
- package/dist/client/versions.js.map +1 -0
- package/dist/client.js +35 -30
- package/dist/components/admin/content-manager.d.ts +80 -0
- package/dist/components/admin/content-manager.js +591 -0
- package/dist/components/admin/content-manager.js.map +1 -0
- package/dist/components/admin/group-manager.d.ts +24 -0
- package/dist/components/admin/group-manager.js +244 -0
- package/dist/components/admin/group-manager.js.map +1 -0
- package/dist/components/admin/media-item-panel.js +1 -1
- package/dist/components/admin/media-item-panel.js.map +1 -1
- package/dist/components/content/card.d.ts +44 -0
- package/dist/components/content/card.js +285 -0
- package/dist/components/content/card.js.map +1 -0
- package/dist/components/content/editors.d.ts +40 -0
- package/dist/components/content/editors.js +764 -0
- package/dist/components/content/editors.js.map +1 -0
- package/dist/components/content/filters.d.ts +28 -0
- package/dist/components/content/filters.js +134 -0
- package/dist/components/content/filters.js.map +1 -0
- package/dist/components/content/grid.d.ts +62 -0
- package/dist/components/content/grid.js +233 -0
- package/dist/components/content/grid.js.map +1 -0
- package/dist/components/content/index.d.ts +8 -0
- package/dist/components/content/listing.d.ts +20 -0
- package/dist/components/content/listing.js +150 -0
- package/dist/components/content/listing.js.map +1 -0
- package/dist/components/content/offer-fields.d.ts +8 -0
- package/dist/components/content/offer-fields.js +127 -0
- package/dist/components/content/offer-fields.js.map +1 -0
- package/dist/components/content/paywall.d.ts +53 -0
- package/dist/components/content/paywall.js +221 -0
- package/dist/components/content/paywall.js.map +1 -0
- package/dist/components/content/search.d.ts +27 -0
- package/dist/components/content/search.js +164 -0
- package/dist/components/content/search.js.map +1 -0
- package/dist/components/groups/banner.d.ts +51 -0
- package/dist/components/groups/banner.js +194 -0
- package/dist/components/groups/banner.js.map +1 -0
- package/dist/components/groups/directory.d.ts +37 -0
- package/dist/components/groups/directory.js +117 -0
- package/dist/components/groups/directory.js.map +1 -0
- package/dist/components/groups/editor.d.ts +44 -0
- package/dist/components/groups/editor.js +860 -0
- package/dist/components/groups/editor.js.map +1 -0
- package/dist/components/groups/fields.d.ts +13 -0
- package/dist/components/groups/fields.js +68 -0
- package/dist/components/groups/fields.js.map +1 -0
- package/dist/components/groups/header.d.ts +25 -0
- package/dist/components/groups/header.js +131 -0
- package/dist/components/groups/header.js.map +1 -0
- package/dist/components/groups/index.d.ts +6 -0
- package/dist/components/groups/memberships.d.ts +55 -0
- package/dist/components/groups/memberships.js +527 -0
- package/dist/components/groups/memberships.js.map +1 -0
- package/dist/components/groups/parts.d.ts +49 -0
- package/dist/components/groups/parts.js +172 -0
- package/dist/components/groups/parts.js.map +1 -0
- package/dist/components/media-folder-editor.js +153 -152
- package/dist/components/media-folder-editor.js.map +1 -1
- package/dist/components/social/comments.d.ts +7 -5
- package/dist/components/social/comments.js +6 -2
- package/dist/components/social/comments.js.map +1 -1
- package/dist/components/social/engagement.d.ts +4 -3
- package/dist/components/social/engagement.js.map +1 -1
- package/dist/components/social/moderation.d.ts +1 -1
- package/dist/components/social/moderation.js +3 -3
- package/dist/components/social/moderation.js.map +1 -1
- package/dist/editor/rich-text-editor.d.ts +4 -4
- package/dist/editor/rich-text-editor.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +23 -9
- package/dist/locales/admin/de.js +5 -66
- package/dist/locales/admin/de.js.map +1 -1
- package/dist/locales/admin/en.d.ts +5 -66
- package/dist/locales/admin/en.js +19 -80
- package/dist/locales/admin/en.js.map +1 -1
- package/dist/locales/admin/es.js +5 -66
- package/dist/locales/admin/es.js.map +1 -1
- package/dist/locales/admin/ja.js +5 -66
- package/dist/locales/admin/ja.js.map +1 -1
- package/dist/locales/admin/ko.js +5 -66
- package/dist/locales/admin/ko.js.map +1 -1
- package/dist/locales/admin/zh.js +5 -66
- package/dist/locales/admin/zh.js.map +1 -1
- package/dist/locales/content/de.d.ts +2 -0
- package/dist/locales/content/de.js +98 -0
- package/dist/locales/content/de.js.map +1 -0
- package/dist/locales/content/en.d.ts +93 -0
- package/dist/locales/content/en.js +98 -0
- package/dist/locales/content/en.js.map +1 -0
- package/dist/locales/content/es.d.ts +2 -0
- package/dist/locales/content/es.js +98 -0
- package/dist/locales/content/es.js.map +1 -0
- package/dist/locales/content/ja.d.ts +2 -0
- package/dist/locales/content/ja.js +98 -0
- package/dist/locales/content/ja.js.map +1 -0
- package/dist/locales/content/ko.d.ts +2 -0
- package/dist/locales/content/ko.js +98 -0
- package/dist/locales/content/ko.js.map +1 -0
- package/dist/locales/content/zh.d.ts +2 -0
- package/dist/locales/content/zh.js +98 -0
- package/dist/locales/content/zh.js.map +1 -0
- package/dist/locales/de.js +11 -2
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/discovery/de.d.ts +2 -0
- package/dist/locales/discovery/de.js +68 -0
- package/dist/locales/discovery/de.js.map +1 -0
- package/dist/locales/discovery/en.d.ts +63 -0
- package/dist/locales/discovery/en.js +68 -0
- package/dist/locales/discovery/en.js.map +1 -0
- package/dist/locales/discovery/es.d.ts +2 -0
- package/dist/locales/discovery/es.js +68 -0
- package/dist/locales/discovery/es.js.map +1 -0
- package/dist/locales/discovery/ja.d.ts +2 -0
- package/dist/locales/discovery/ja.js +68 -0
- package/dist/locales/discovery/ja.js.map +1 -0
- package/dist/locales/discovery/ko.d.ts +2 -0
- package/dist/locales/discovery/ko.js +68 -0
- package/dist/locales/discovery/ko.js.map +1 -0
- package/dist/locales/discovery/zh.d.ts +2 -0
- package/dist/locales/discovery/zh.js +68 -0
- package/dist/locales/discovery/zh.js.map +1 -0
- package/dist/locales/en.d.ts +288 -67
- package/dist/locales/en.js +11 -2
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +11 -2
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/groups/de.d.ts +2 -0
- package/dist/locales/groups/de.js +133 -0
- package/dist/locales/groups/de.js.map +1 -0
- package/dist/locales/groups/en.d.ts +128 -0
- package/dist/locales/groups/en.js +133 -0
- package/dist/locales/groups/en.js.map +1 -0
- package/dist/locales/groups/es.d.ts +2 -0
- package/dist/locales/groups/es.js +133 -0
- package/dist/locales/groups/es.js.map +1 -0
- package/dist/locales/groups/ja.d.ts +2 -0
- package/dist/locales/groups/ja.js +133 -0
- package/dist/locales/groups/ja.js.map +1 -0
- package/dist/locales/groups/ko.d.ts +2 -0
- package/dist/locales/groups/ko.js +133 -0
- package/dist/locales/groups/ko.js.map +1 -0
- package/dist/locales/groups/zh.d.ts +2 -0
- package/dist/locales/groups/zh.js +133 -0
- package/dist/locales/groups/zh.js.map +1 -0
- package/dist/locales/ja.js +11 -2
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +11 -2
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/social/de.js +2 -1
- package/dist/locales/social/de.js.map +1 -1
- package/dist/locales/social/en.d.ts +2 -1
- package/dist/locales/social/en.js +2 -1
- package/dist/locales/social/en.js.map +1 -1
- package/dist/locales/social/es.js +2 -1
- package/dist/locales/social/es.js.map +1 -1
- package/dist/locales/social/ja.js +2 -1
- package/dist/locales/social/ja.js.map +1 -1
- package/dist/locales/social/ko.js +2 -1
- package/dist/locales/social/ko.js.map +1 -1
- package/dist/locales/social/zh.js +2 -1
- package/dist/locales/social/zh.js.map +1 -1
- package/dist/locales/zh.js +11 -2
- package/dist/locales/zh.js.map +1 -1
- package/dist/react/admin.d.ts +1 -1
- package/dist/react/admin.js +8 -6
- package/dist/react/admin.js.map +1 -1
- package/dist/react/comments.d.ts +9 -7
- package/dist/react/comments.js +42 -27
- package/dist/react/comments.js.map +1 -1
- package/dist/react/contents.d.ts +57 -0
- package/dist/react/contents.js +214 -0
- package/dist/react/contents.js.map +1 -0
- package/dist/react/context.d.ts +1 -1
- package/dist/react/context.js.map +1 -1
- package/dist/react/crop.js +45 -44
- package/dist/react/crop.js.map +1 -1
- package/dist/react/detail.d.ts +30 -0
- package/dist/react/detail.js +42 -0
- package/dist/react/detail.js.map +1 -0
- package/dist/react/discovery.d.ts +138 -0
- package/dist/react/discovery.js +299 -0
- package/dist/react/discovery.js.map +1 -0
- package/dist/react/engagement.d.ts +5 -5
- package/dist/react/engagement.js +55 -41
- package/dist/react/engagement.js.map +1 -1
- package/dist/react/folder.js +54 -53
- package/dist/react/folder.js.map +1 -1
- package/dist/react/groups.d.ts +115 -0
- package/dist/react/groups.js +229 -0
- package/dist/react/groups.js.map +1 -0
- package/dist/react/index.d.ts +5 -1
- package/dist/react/public.d.ts +6 -5
- package/dist/react/public.js +19 -22
- package/dist/react/public.js.map +1 -1
- package/dist/react/read.js +105 -106
- package/dist/react/read.js.map +1 -1
- package/dist/react/resources.d.ts +2 -2
- package/dist/react/resources.js +69 -40
- package/dist/react/resources.js.map +1 -1
- package/dist/react/store.js +41 -39
- package/dist/react/store.js.map +1 -1
- package/dist/react/upload.js +54 -53
- package/dist/react/upload.js.map +1 -1
- package/dist/react/use-resource.d.ts +2 -1
- package/dist/react/use-resource.js +1 -0
- package/dist/react/use-resource.js.map +1 -1
- package/dist/react/versions.d.ts +57 -0
- package/dist/react/versions.js +94 -0
- package/dist/react/versions.js.map +1 -0
- package/dist/react/video.js +87 -86
- package/dist/react/video.js.map +1 -1
- package/dist/react.js +9 -5
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/dist/client/content/posts.d.ts +0 -52
- package/dist/client/content/posts.js +0 -84
- package/dist/client/content/posts.js.map +0 -1
- package/dist/client/taxonomy.d.ts +0 -34
- package/dist/client/taxonomy.js +0 -84
- package/dist/client/taxonomy.js.map +0 -1
- package/dist/components/admin/post-manager.d.ts +0 -45
- package/dist/components/admin/post-manager.js +0 -602
- package/dist/components/admin/post-manager.js.map +0 -1
- package/dist/components/admin/taxonomy-manager.d.ts +0 -54
- package/dist/components/admin/taxonomy-manager.js +0 -976
- package/dist/components/admin/taxonomy-manager.js.map +0 -1
- package/dist/react/posts.d.ts +0 -61
- package/dist/react/posts.js +0 -183
- package/dist/react/posts.js.map +0 -1
package/dist/client/index.d.ts
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
export { createContentKitClient, type ContentKitClient, type ContentKitClientOptions } from './client.js';
|
|
2
2
|
export type { ContentKitChange, ContentKitModule, ContentKitMounts, Mount, TokenSource } from './http.js';
|
|
3
3
|
export { CLIENT_ERROR_CODES, ContentKitError, ERROR_CODES, failureError, isContentKitError, readContentKitError, toContentKitError, type ContentKitErrorCode, type ContentKitErrorInit, } from './errors.js';
|
|
4
|
-
export { IMAGE_SCHEME, imageRef, imageRefs,
|
|
4
|
+
export { BODY_SUFFIX, IMAGE_SCHEME, imageRef, imageRefs, ContentsClient, type ManageQuery } from './contents.js';
|
|
5
|
+
export { CatalogClient, type CatalogTotalsQuery } from './catalog.js';
|
|
5
6
|
export { CommentsClient, type AdminCommentQuery, type CommentQuery } from './content/comments.js';
|
|
6
7
|
export { ReactionsClient } from './content/reactions.js';
|
|
7
|
-
export { FavoritesClient } from './content/favorites.js';
|
|
8
|
+
export { FavoritesClient, type MineQuery } from './content/favorites.js';
|
|
8
9
|
export { PollsClient, type PollAdminQuery, type PollQuery, type PollState } from './content/polls.js';
|
|
9
10
|
export { BansClient } from './content/bans.js';
|
|
10
11
|
export { ModerationClient, type HeldQuery } from './content/moderation.js';
|
|
11
|
-
export { reactionVerb, type BanScope, type ContentChange, type Decision, type HeldKind, type Reaction, type Sort } from './content/types.js';
|
|
12
|
-
export {
|
|
12
|
+
export { reactionVerb, sameTarget, type BanScope, type ContentChange, type Decision, type HeldKind, type Reaction, type Sort, type Target } from './content/types.js';
|
|
13
|
+
export { GroupsClient, type GroupImageSlot, type GroupQuery, type MemberQuery, type MyGroupQuery } from './groups.js';
|
|
14
|
+
export { DiscoveryClient, type AccessMode, type CardQuery, type ContentFilterQuery, type ContentQuery, type HistoryQuery, type ListSort, type Period, type RailQuery, type ReaderQuery, type RecentSearchesQuery, type ReleaseDatesQuery, type SearchQuery, type TypeaheadQuery, } from './discovery.js';
|
|
15
|
+
export { VersionsClient, isPending } from './versions.js';
|
|
16
|
+
export { OffersClient } from './offers.js';
|
|
17
|
+
export { Beacons, eventId, readerSession, type Attribution, type BeaconOptions, type ViewSession } from './beacons.js';
|
|
18
|
+
export { LETTERS, attributeInput, fold, fromLocalInput, humanize, letterOf, parseAttribute, parseAttributes, slugify, toLocalInput } from './group-fields.js';
|
|
13
19
|
export { CodesClient } from './codes.js';
|
|
14
|
-
export { CatalogClient, type CatalogTotalsQuery } from './catalog.js';
|
|
15
20
|
export { NotificationsClient, type NotificationItem } from './notifications.js';
|
|
16
21
|
export { call, routeURL, type CallOptions, type Method, type PageQuery, type PathParams, type RouteQuery, type RoutePath } from './route.js';
|
|
17
22
|
export { MediaApi, type ReadOptions } from './media/api.js';
|
|
@@ -20,7 +25,7 @@ export { ItemUploadQueue, type ItemStatus, type QueueItem, type QueueOptions, ty
|
|
|
20
25
|
export { acceptOf, defaultImage, filesFor, isPattern, ratioLabel, ruleDir, ruleFor, screenFiles, uniqueName, uploadRules, withMediaType, type Screened, } from './media/rules.js';
|
|
21
26
|
export { READ_CHUNK, chunkOffsets, isProcessing, mergeReads, processing } from './media/windows.js';
|
|
22
27
|
export { defaultTransport, fetchTransport, xhrTransport, type Transport } from './media/transport.js';
|
|
23
|
-
export { checkRef, isContentId } from './media/ref.js';
|
|
28
|
+
export { checkRef, isContentId, itemKey, sameItem } from './media/ref.js';
|
|
24
29
|
export { encodeRemaining } from './media/encode.js';
|
|
25
30
|
export { sha256Hex } from './media/hash.js';
|
|
26
31
|
export { Pacer, type PacerOptions } from './media/pacer.js';
|
|
@@ -30,7 +30,7 @@ export declare class MediaApi {
|
|
|
30
30
|
read(ref: RefBody, o?: ReadOptions, signal?: AbortSignal): Promise<ReadResult>;
|
|
31
31
|
/** Every kind's public presets (the same for every viewer). */
|
|
32
32
|
presets(signal?: AbortSignal): Promise<PresetRule[]>;
|
|
33
|
-
/** The item under the read API: `{media}/{kind}/{id}`. */
|
|
33
|
+
/** The item under the read API: `{media}/{kind}/{id}`, a version's at `…/versions/{version}`. */
|
|
34
34
|
itemURL(ref: RefBody): string;
|
|
35
35
|
private post;
|
|
36
36
|
}
|
package/dist/client/media/api.js
CHANGED
|
@@ -31,7 +31,7 @@ var t = class {
|
|
|
31
31
|
path: n,
|
|
32
32
|
t: String(r)
|
|
33
33
|
});
|
|
34
|
-
return i && o.set("w", String(i)), this.http.request(`${this.http.mount("upload", t)}/frame?${o}`, {
|
|
34
|
+
return t.version && o.set("version", t.version), i && o.set("w", String(i)), this.http.request(`${this.http.mount("upload", t)}/frame?${o}`, {
|
|
35
35
|
signal: a,
|
|
36
36
|
blob: !0
|
|
37
37
|
});
|
|
@@ -46,7 +46,9 @@ var t = class {
|
|
|
46
46
|
return this.http.request(`${this.http.mount("media")}/presets`, { signal: e });
|
|
47
47
|
}
|
|
48
48
|
itemURL(t) {
|
|
49
|
-
|
|
49
|
+
e(t);
|
|
50
|
+
let n = `${this.http.mount("media", t)}/${encodeURIComponent(t.kind)}/${encodeURIComponent(t.id)}`;
|
|
51
|
+
return t.version ? `${n}/versions/${encodeURIComponent(t.version)}` : n;
|
|
50
52
|
}
|
|
51
53
|
async post(t, n, r, i) {
|
|
52
54
|
return e(t), this.http.request(this.http.mount("upload", t) + n, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","names":[],"sources":["../../../src/client/media/api.ts"],"sourcesContent":["import type { Http } from \"../http.js\";\nimport type {\n CommitBody,\n CommitReply,\n CompleteReply,\n PartsBody,\n PartsReply,\n PresetRule,\n PresignBody,\n PresignReply,\n ReadResult,\n RefBody,\n TicketBody,\n} from \"../generated/wire.js\";\nimport { checkRef } from \"./ref.js\";\n\n/** What a read returns (Reader.Read's ReadOptions). */\nexport interface ReadOptions {\n /** Only files under this path prefix, e.g. \"low-res/\". */\n prefix?: string;\n /** The range of files that get URLs. */\n offset?: number;\n limit?: number;\n /** Sign each file's download name into its URL. */\n download?: boolean;\n /** Editors also get the uploads: edit, frame, pending, failure and editor view. */\n editor?: boolean;\n}\n\n/**\n * The upload API (media.UploadHandler) and read API (Reader.Handler), one\n * method per route. Each takes the item, whose mount the call goes to.\n */\nexport class MediaApi {\n constructor(private readonly http: Http) {}\n\n presign(b: PresignBody, signal?: AbortSignal) {\n return this.post<PresignReply>(b.ref, \"/presign\", b, signal);\n }\n parts(ref: RefBody, b: PartsBody, signal?: AbortSignal) {\n return this.post<PartsReply>(ref, \"/parts\", b, signal);\n }\n listParts(ref: RefBody, b: TicketBody, signal?: AbortSignal) {\n return this.post<PartsReply>(ref, \"/parts/list\", b, signal);\n }\n complete(ref: RefBody, b: TicketBody, signal?: AbortSignal) {\n return this.post<CompleteReply>(ref, \"/complete\", b, signal);\n }\n abort(ref: RefBody, b: TicketBody, signal?: AbortSignal) {\n return this.post<void>(ref, \"/abort\", b, signal);\n }\n commit(b: CommitBody, signal?: AbortSignal) {\n return this.post<CommitReply>(b.ref, \"/commit\", b, signal);\n }\n /** A JPEG still of the video upload at path, t seconds in, w pixels wide (0: the frame's). */\n async frame(ref: RefBody, path: string, t: number, w?: number, signal?: AbortSignal) {\n checkRef(ref);\n const q = new URLSearchParams({ kind: ref.kind, id: ref.id, path, t: String(t) });\n if (w) q.set(\"w\", String(w));\n return this.http.request<Blob>(`${this.http.mount(\"upload\", ref)}/frame?${q}`, { signal, blob: true });\n }\n\n async read(ref: RefBody, o: ReadOptions = {}, signal?: AbortSignal) {\n const q = new URLSearchParams();\n if (o.prefix) q.set(\"prefix\", o.prefix);\n if (o.offset) q.set(\"offset\", String(o.offset));\n if (o.limit) q.set(\"limit\", String(o.limit));\n if (o.download) q.set(\"download\", \"1\");\n if (o.editor) q.set(\"editor\", \"1\");\n const s = q.toString();\n return this.http.request<ReadResult>(this.itemURL(ref) + (s ? \"?\" + s : \"\"), { signal });\n }\n\n /** Every kind's public presets (the same for every viewer). */\n presets(signal?: AbortSignal) {\n return this.http.request<PresetRule[]>(`${this.http.mount(\"media\")}/presets`, { signal });\n }\n\n /** The item under the read API: `{media}/{kind}/{id}`. */\n itemURL(ref: RefBody): string {\n checkRef(ref);\n
|
|
1
|
+
{"version":3,"file":"api.js","names":[],"sources":["../../../src/client/media/api.ts"],"sourcesContent":["import type { Http } from \"../http.js\";\nimport type {\n CommitBody,\n CommitReply,\n CompleteReply,\n PartsBody,\n PartsReply,\n PresetRule,\n PresignBody,\n PresignReply,\n ReadResult,\n RefBody,\n TicketBody,\n} from \"../generated/wire.js\";\nimport { checkRef } from \"./ref.js\";\n\n/** What a read returns (Reader.Read's ReadOptions). */\nexport interface ReadOptions {\n /** Only files under this path prefix, e.g. \"low-res/\". */\n prefix?: string;\n /** The range of files that get URLs. */\n offset?: number;\n limit?: number;\n /** Sign each file's download name into its URL. */\n download?: boolean;\n /** Editors also get the uploads: edit, frame, pending, failure and editor view. */\n editor?: boolean;\n}\n\n/**\n * The upload API (media.UploadHandler) and read API (Reader.Handler), one\n * method per route. Each takes the item, whose mount the call goes to.\n */\nexport class MediaApi {\n constructor(private readonly http: Http) {}\n\n presign(b: PresignBody, signal?: AbortSignal) {\n return this.post<PresignReply>(b.ref, \"/presign\", b, signal);\n }\n parts(ref: RefBody, b: PartsBody, signal?: AbortSignal) {\n return this.post<PartsReply>(ref, \"/parts\", b, signal);\n }\n listParts(ref: RefBody, b: TicketBody, signal?: AbortSignal) {\n return this.post<PartsReply>(ref, \"/parts/list\", b, signal);\n }\n complete(ref: RefBody, b: TicketBody, signal?: AbortSignal) {\n return this.post<CompleteReply>(ref, \"/complete\", b, signal);\n }\n abort(ref: RefBody, b: TicketBody, signal?: AbortSignal) {\n return this.post<void>(ref, \"/abort\", b, signal);\n }\n commit(b: CommitBody, signal?: AbortSignal) {\n return this.post<CommitReply>(b.ref, \"/commit\", b, signal);\n }\n /** A JPEG still of the video upload at path, t seconds in, w pixels wide (0: the frame's). */\n async frame(ref: RefBody, path: string, t: number, w?: number, signal?: AbortSignal) {\n checkRef(ref);\n const q = new URLSearchParams({ kind: ref.kind, id: ref.id, path, t: String(t) });\n if (ref.version) q.set(\"version\", ref.version);\n if (w) q.set(\"w\", String(w));\n return this.http.request<Blob>(`${this.http.mount(\"upload\", ref)}/frame?${q}`, { signal, blob: true });\n }\n\n async read(ref: RefBody, o: ReadOptions = {}, signal?: AbortSignal) {\n const q = new URLSearchParams();\n if (o.prefix) q.set(\"prefix\", o.prefix);\n if (o.offset) q.set(\"offset\", String(o.offset));\n if (o.limit) q.set(\"limit\", String(o.limit));\n if (o.download) q.set(\"download\", \"1\");\n if (o.editor) q.set(\"editor\", \"1\");\n const s = q.toString();\n return this.http.request<ReadResult>(this.itemURL(ref) + (s ? \"?\" + s : \"\"), { signal });\n }\n\n /** Every kind's public presets (the same for every viewer). */\n presets(signal?: AbortSignal) {\n return this.http.request<PresetRule[]>(`${this.http.mount(\"media\")}/presets`, { signal });\n }\n\n /** The item under the read API: `{media}/{kind}/{id}`, a version's at `…/versions/{version}`. */\n itemURL(ref: RefBody): string {\n checkRef(ref);\n const item = `${this.http.mount(\"media\", ref)}/${encodeURIComponent(ref.kind)}/${encodeURIComponent(ref.id)}`;\n return ref.version ? `${item}/versions/${encodeURIComponent(ref.version)}` : item;\n }\n\n private async post<T>(ref: RefBody, path: string, body: object, signal?: AbortSignal) {\n checkRef(ref);\n return this.http.request<T>(this.http.mount(\"upload\", ref) + path, { body, signal });\n }\n}\n"],"mappings":";;AAiCA,IAAa,IAAb,MAAsB;CACS;CAA7B,YAAY,GAA6B;EAAZ,KAAA,OAAA;CAAa;CAE1C,QAAQ,GAAgB,GAAsB;EAC5C,OAAO,KAAK,KAAmB,EAAE,KAAK,YAAY,GAAG,CAAM;CAC7D;CACA,MAAM,GAAc,GAAc,GAAsB;EACtD,OAAO,KAAK,KAAiB,GAAK,UAAU,GAAG,CAAM;CACvD;CACA,UAAU,GAAc,GAAe,GAAsB;EAC3D,OAAO,KAAK,KAAiB,GAAK,eAAe,GAAG,CAAM;CAC5D;CACA,SAAS,GAAc,GAAe,GAAsB;EAC1D,OAAO,KAAK,KAAoB,GAAK,aAAa,GAAG,CAAM;CAC7D;CACA,MAAM,GAAc,GAAe,GAAsB;EACvD,OAAO,KAAK,KAAW,GAAK,UAAU,GAAG,CAAM;CACjD;CACA,OAAO,GAAe,GAAsB;EAC1C,OAAO,KAAK,KAAkB,EAAE,KAAK,WAAW,GAAG,CAAM;CAC3D;CAEA,MAAM,MAAM,GAAc,GAAc,GAAW,GAAY,GAAsB;EACnF,EAAS,CAAG;EACZ,IAAM,IAAI,IAAI,gBAAgB;GAAE,MAAM,EAAI;GAAM,IAAI,EAAI;GAAI;GAAM,GAAG,OAAO,CAAC;EAAE,CAAC;EAGhF,OAFI,EAAI,WAAS,EAAE,IAAI,WAAW,EAAI,OAAO,GACzC,KAAG,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,GACpB,KAAK,KAAK,QAAc,GAAG,KAAK,KAAK,MAAM,UAAU,CAAG,EAAE,SAAS,KAAK;GAAE;GAAQ,MAAM;EAAK,CAAC;CACvG;CAEA,MAAM,KAAK,GAAc,IAAiB,CAAC,GAAG,GAAsB;EAClE,IAAM,IAAI,IAAI,gBAAgB;EAK9B,AAJI,EAAE,UAAQ,EAAE,IAAI,UAAU,EAAE,MAAM,GAClC,EAAE,UAAQ,EAAE,IAAI,UAAU,OAAO,EAAE,MAAM,CAAC,GAC1C,EAAE,SAAO,EAAE,IAAI,SAAS,OAAO,EAAE,KAAK,CAAC,GACvC,EAAE,YAAU,EAAE,IAAI,YAAY,GAAG,GACjC,EAAE,UAAQ,EAAE,IAAI,UAAU,GAAG;EACjC,IAAM,IAAI,EAAE,SAAS;EACrB,OAAO,KAAK,KAAK,QAAoB,KAAK,QAAQ,CAAG,KAAK,IAAI,MAAM,IAAI,KAAK,EAAE,UAAO,CAAC;CACzF;CAGA,QAAQ,GAAsB;EAC5B,OAAO,KAAK,KAAK,QAAsB,GAAG,KAAK,KAAK,MAAM,OAAO,EAAE,WAAW,EAAE,UAAO,CAAC;CAC1F;CAGA,QAAQ,GAAsB;EAC5B,EAAS,CAAG;EACZ,IAAM,IAAO,GAAG,KAAK,KAAK,MAAM,SAAS,CAAG,EAAE,GAAG,mBAAmB,EAAI,IAAI,EAAE,GAAG,mBAAmB,EAAI,EAAE;EAC1G,OAAO,EAAI,UAAU,GAAG,EAAK,YAAY,mBAAmB,EAAI,OAAO,MAAM;CAC/E;CAEA,MAAc,KAAQ,GAAc,GAAc,GAAc,GAAsB;EAEpF,OADA,EAAS,CAAG,GACL,KAAK,KAAK,QAAW,KAAK,KAAK,MAAM,UAAU,CAAG,IAAI,GAAM;GAAE;GAAM;EAAO,CAAC;CACrF;AACF"}
|
|
@@ -111,6 +111,8 @@ export interface NamedOptions {
|
|
|
111
111
|
wait?: boolean;
|
|
112
112
|
/** How long to wait, ms. */
|
|
113
113
|
timeout?: number;
|
|
114
|
+
/** A crop or rotation the file is committed with. */
|
|
115
|
+
edit?: Edit;
|
|
114
116
|
}
|
|
115
117
|
/** An upload to a Named path, committed. */
|
|
116
118
|
export interface NamedUpload {
|
|
@@ -119,10 +121,8 @@ export interface NamedUpload {
|
|
|
119
121
|
path: string;
|
|
120
122
|
file: FileInfo;
|
|
121
123
|
}
|
|
122
|
-
/** The media
|
|
124
|
+
/** The media kind of poll folders (content.Media.PollKind). A content item's folder is its kind's. */
|
|
123
125
|
export interface ContentFolders {
|
|
124
|
-
/** Default "post". */
|
|
125
|
-
post?: string;
|
|
126
126
|
/** Default "poll". */
|
|
127
127
|
poll?: string;
|
|
128
128
|
}
|
|
@@ -227,13 +227,12 @@ export declare class MediaClient {
|
|
|
227
227
|
*/
|
|
228
228
|
uploadNamed(ref: RefBody, file: Uploadable, o?: NamedOptions): Promise<NamedUpload>;
|
|
229
229
|
/**
|
|
230
|
-
* An image for a
|
|
231
|
-
*
|
|
232
|
-
*
|
|
230
|
+
* An image for a content item's rich-text body, uploaded to the item's body
|
|
231
|
+
* folder ({kind}.body and its id, BODY_SUFFIX), which only viewers who open
|
|
232
|
+
* the item see; resolves with its reference to store in the body (`ref`) and
|
|
233
|
+
* a URL that shows it to the editor now (`url`, null while none exists).
|
|
233
234
|
*/
|
|
234
|
-
uploadInline(
|
|
235
|
-
/** The media folder of a post. */
|
|
236
|
-
postRef(id: string): RefBody;
|
|
235
|
+
uploadInline(item: RefBody, file: Uploadable, o?: NamedOptions): Promise<NamedUpload & InlineImage>;
|
|
237
236
|
/** The media folder of a poll. */
|
|
238
237
|
pollRef(id: string): RefBody;
|
|
239
238
|
/** Discards a paused multipart upload. */
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { ContentKitError as e, aborted as t, failureError as n, throwIfAborted as r, toContentKitError as i } from "../errors.js";
|
|
2
2
|
import { call as a } from "../route.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { itemRef as o } from "./ref.js";
|
|
4
|
+
import { MediaApi as s } from "./api.js";
|
|
5
|
+
import { sha256Hex as c } from "./hash.js";
|
|
6
|
+
import { Pacer as l } from "./pacer.js";
|
|
7
|
+
import { defaultTransport as u } from "./transport.js";
|
|
7
8
|
//#region src/client/media/client.ts
|
|
8
|
-
var
|
|
9
|
+
var d = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, f = (e, t) => e === t || p(e) === t, p = (e) => {
|
|
9
10
|
let t = e.lastIndexOf(".");
|
|
10
11
|
return t > e.lastIndexOf("/") + 1 ? e.slice(0, t) : e;
|
|
11
|
-
},
|
|
12
|
+
}, m = class {
|
|
12
13
|
http;
|
|
13
14
|
api;
|
|
14
15
|
transport;
|
|
@@ -19,29 +20,26 @@ var u = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, d =
|
|
|
19
20
|
presetList;
|
|
20
21
|
folders;
|
|
21
22
|
constructor(e, t = {}, n = {}) {
|
|
22
|
-
this.http = e, this.folders = {
|
|
23
|
-
post: n.post ?? "post",
|
|
24
|
-
poll: n.poll ?? "poll"
|
|
25
|
-
}, this.api = new o(e), this.transport = t.transport ?? l, this.concurrency = t.concurrency ?? 4, this.retries = t.retries ?? 5, this.delay = t.retryDelay ?? h, this.targetSeconds = t.targetPartSeconds ?? 30;
|
|
23
|
+
this.http = e, this.folders = { poll: n.poll ?? "poll" }, this.api = new s(e), this.transport = t.transport ?? u, this.concurrency = t.concurrency ?? 4, this.retries = t.retries ?? 5, this.delay = t.retryDelay ?? g, this.targetSeconds = t.targetPartSeconds ?? 30;
|
|
26
24
|
}
|
|
27
25
|
async upload(t, n) {
|
|
28
26
|
let i = n.type || t.type;
|
|
29
27
|
if (!i) throw new e("invalid_request", "the file has no content type");
|
|
30
28
|
if (r(n.signal), n.resume) return this.resumeMultipart(t, n.resume, n);
|
|
31
|
-
let a = t.size, o = await
|
|
29
|
+
let a = t.size, o = await c(t, {
|
|
32
30
|
signal: n.signal,
|
|
33
31
|
onProgress: (e) => n.onProgress?.({
|
|
34
32
|
phase: "hashing",
|
|
35
33
|
loaded: e,
|
|
36
34
|
total: a
|
|
37
35
|
})
|
|
38
|
-
}),
|
|
36
|
+
}), s = () => this.api.presign({
|
|
39
37
|
ref: n.ref,
|
|
40
38
|
path: n.path,
|
|
41
39
|
type: i,
|
|
42
40
|
size: a,
|
|
43
41
|
sha256: o
|
|
44
|
-
}, n.signal), l = await
|
|
42
|
+
}, n.signal), l = await s(), u = (e) => ({
|
|
45
43
|
path: e.path,
|
|
46
44
|
blob: e.blob,
|
|
47
45
|
type: i,
|
|
@@ -58,7 +56,7 @@ var u = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, d =
|
|
|
58
56
|
type: i,
|
|
59
57
|
size: a,
|
|
60
58
|
ref: n.ref,
|
|
61
|
-
file:
|
|
59
|
+
file: h(t),
|
|
62
60
|
limits: {
|
|
63
61
|
minPartSize: e.min_part_size,
|
|
64
62
|
maxPartSize: e.max_part_size,
|
|
@@ -79,7 +77,7 @@ var u = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, d =
|
|
|
79
77
|
})
|
|
80
78
|
});
|
|
81
79
|
}, n.signal, async (e) => {
|
|
82
|
-
e.code === "storage" && e.status === 403 && (l = await
|
|
80
|
+
e.code === "storage" && e.status === 403 && (l = await s());
|
|
83
81
|
}), u(l);
|
|
84
82
|
}
|
|
85
83
|
async put(e, t) {
|
|
@@ -117,15 +115,15 @@ var u = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, d =
|
|
|
117
115
|
operation_id: r.operationID ?? crypto.randomUUID()
|
|
118
116
|
});
|
|
119
117
|
r.onState?.(structuredClone(a));
|
|
120
|
-
let
|
|
118
|
+
let s;
|
|
121
119
|
try {
|
|
122
|
-
|
|
120
|
+
s = (await this.retry(() => this.api.commit(a, r.signal), r.signal)).files;
|
|
123
121
|
} catch (t) {
|
|
124
122
|
if (!(t instanceof e) || t.code !== "not_uploaded") throw t;
|
|
125
|
-
let n = r.sources ?? {}, i = a.ops.filter((e) => e.op === "put" && e.blob && e.blob in n),
|
|
126
|
-
if (
|
|
123
|
+
let n = r.sources ?? {}, i = a.ops.filter((e) => e.op === "put" && e.blob && e.blob in n), o = [...new Set(i.map((e) => e.blob))].filter((e) => !t.blobs || t.blobs.includes(e));
|
|
124
|
+
if (o.length === 0) throw t;
|
|
127
125
|
let c = /* @__PURE__ */ new Map();
|
|
128
|
-
for (let e of
|
|
126
|
+
for (let e of o) {
|
|
129
127
|
let t = n[e], o = t instanceof Blob ? t : t.file, s = t instanceof Blob ? void 0 : t.type, l = i.find((t) => t.blob === e);
|
|
130
128
|
c.set(e, (await this.upload(o, {
|
|
131
129
|
ref: a.ref,
|
|
@@ -142,41 +140,35 @@ var u = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, d =
|
|
|
142
140
|
ops: l,
|
|
143
141
|
operation_id: crypto.randomUUID()
|
|
144
142
|
};
|
|
145
|
-
r.onState?.(structuredClone(u)),
|
|
143
|
+
r.onState?.(structuredClone(u)), s = (await this.retry(() => this.api.commit(u, r.signal), r.signal)).files;
|
|
146
144
|
}
|
|
147
145
|
return i({
|
|
148
146
|
type: "media.committed",
|
|
149
|
-
ref:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
},
|
|
153
|
-
files: o
|
|
154
|
-
}), o;
|
|
147
|
+
ref: o(t),
|
|
148
|
+
files: s
|
|
149
|
+
}), s;
|
|
155
150
|
}
|
|
156
151
|
async read(e, t = {}) {
|
|
157
152
|
return this.api.itemURL(e), this.retry(() => this.api.read(e, t, t.signal), t.signal);
|
|
158
153
|
}
|
|
159
154
|
async waitFor(t, r, i = {}) {
|
|
160
|
-
let a = this.http.captureChanges(),
|
|
155
|
+
let a = this.http.captureChanges(), s = Date.now() + (i.timeout ?? 12e4);
|
|
161
156
|
for (;;) {
|
|
162
|
-
let
|
|
157
|
+
let c = await this.read(t, {
|
|
163
158
|
editor: !0,
|
|
164
|
-
prefix:
|
|
159
|
+
prefix: p(r),
|
|
165
160
|
signal: i.signal
|
|
166
|
-
}),
|
|
167
|
-
if (!
|
|
168
|
-
if (
|
|
169
|
-
if (!
|
|
161
|
+
}), l = c.files.find((e) => e.upload && f(e.path, r));
|
|
162
|
+
if (!l) throw new e("not_found", `no upload ${r}`, { status: 404 });
|
|
163
|
+
if (l.failed) throw n(l.failed);
|
|
164
|
+
if (!l.pending?.length && !l.staged && (l.size ?? 0) > 0) return a({
|
|
170
165
|
type: "media.processed",
|
|
171
|
-
ref:
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (s.full) throw new e("too_large", `${r} waits: the item is full; remove uploads to process more`, { status: 413 });
|
|
178
|
-
if (Date.now() >= o) throw new e("render_timeout", `${r} is still processing`);
|
|
179
|
-
await g(i.interval ?? 1e3, i.signal);
|
|
166
|
+
ref: o(t),
|
|
167
|
+
file: l
|
|
168
|
+
}), l;
|
|
169
|
+
if (c.full) throw new e("too_large", `${r} waits: the item is full; remove uploads to process more`, { status: 413 });
|
|
170
|
+
if (Date.now() >= s) throw new e("render_timeout", `${r} is still processing`);
|
|
171
|
+
await _(i.interval ?? 1e3, i.signal);
|
|
180
172
|
}
|
|
181
173
|
}
|
|
182
174
|
async editorView(t, r, i = {}) {
|
|
@@ -184,9 +176,9 @@ var u = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, d =
|
|
|
184
176
|
for (;;) {
|
|
185
177
|
let o = (await this.read(t, {
|
|
186
178
|
editor: !0,
|
|
187
|
-
prefix:
|
|
179
|
+
prefix: p(r),
|
|
188
180
|
signal: i.signal
|
|
189
|
-
})).files.find((e) => e.upload &&
|
|
181
|
+
})).files.find((e) => e.upload && f(e.path, r));
|
|
190
182
|
if (!o) throw new e("not_found", `no upload ${r}`, { status: 404 });
|
|
191
183
|
if (o.editor_url && o.w && o.h) return {
|
|
192
184
|
url: o.editor_url,
|
|
@@ -196,7 +188,7 @@ var u = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, d =
|
|
|
196
188
|
};
|
|
197
189
|
if (o.failed) throw n(o.failed);
|
|
198
190
|
if (Date.now() >= a) throw new e("render_timeout", `the editor view of ${r} is still rendering`);
|
|
199
|
-
await
|
|
191
|
+
await _(i.interval ?? 1e3, i.signal);
|
|
200
192
|
}
|
|
201
193
|
}
|
|
202
194
|
presets() {
|
|
@@ -220,9 +212,10 @@ var u = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, d =
|
|
|
220
212
|
signal: r.signal,
|
|
221
213
|
onProgress: r.onProgress,
|
|
222
214
|
wait: r.wait,
|
|
223
|
-
timeout: r.timeout
|
|
224
|
-
|
|
225
|
-
|
|
215
|
+
timeout: r.timeout,
|
|
216
|
+
edit: r.edit
|
|
217
|
+
}), a = p(i.path.slice(i.path.lastIndexOf("/") + 1));
|
|
218
|
+
if (!d.test(a)) throw new e("invalid_request", `${i.path} is not a server-named upload path of ${t.kind}`);
|
|
226
219
|
return {
|
|
227
220
|
name: a,
|
|
228
221
|
path: i.path,
|
|
@@ -230,8 +223,8 @@ var u = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, d =
|
|
|
230
223
|
};
|
|
231
224
|
}
|
|
232
225
|
async uploadInline(e, t, n = {}) {
|
|
233
|
-
let r = await this.uploadNamed(
|
|
234
|
-
params: { id: e },
|
|
226
|
+
let r = await this.uploadNamed(e, t, n), i = await a(this.http, "POST", "/contents/{id}/images", {
|
|
227
|
+
params: { id: e.id },
|
|
235
228
|
body: { image: r.name },
|
|
236
229
|
signal: n.signal
|
|
237
230
|
});
|
|
@@ -240,12 +233,6 @@ var u = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, d =
|
|
|
240
233
|
...i
|
|
241
234
|
};
|
|
242
235
|
}
|
|
243
|
-
postRef(e) {
|
|
244
|
-
return {
|
|
245
|
-
kind: this.folders.post,
|
|
246
|
-
id: e
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
236
|
pollRef(e) {
|
|
250
237
|
return {
|
|
251
238
|
kind: this.folders.poll,
|
|
@@ -256,7 +243,7 @@ var u = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, d =
|
|
|
256
243
|
await this.api.abort(e.ref, { ticket: e.ticket });
|
|
257
244
|
}
|
|
258
245
|
async resumeMultipart(t, n, r) {
|
|
259
|
-
let i =
|
|
246
|
+
let i = h(t);
|
|
260
247
|
if (i.size !== n.size || i.name !== n.file.name || i.lastModified !== n.file.lastModified) throw new e("resume_mismatch", "the saved upload is for a different file");
|
|
261
248
|
let a;
|
|
262
249
|
try {
|
|
@@ -277,13 +264,13 @@ var u = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, d =
|
|
|
277
264
|
return this.multipart(t, structuredClone(n), o, r);
|
|
278
265
|
}
|
|
279
266
|
async multipart(e, n, r, i) {
|
|
280
|
-
let a = new
|
|
267
|
+
let a = new l({
|
|
281
268
|
...n.limits,
|
|
282
269
|
maxConcurrency: this.concurrency,
|
|
283
270
|
targetSeconds: this.targetSeconds
|
|
284
|
-
}), o = () => i.onState?.(structuredClone(n)),
|
|
271
|
+
}), o = () => i.onState?.(structuredClone(n)), s = /* @__PURE__ */ new Map(), u = n.parts.filter((e) => r.has(e.number)).reduce((e, t) => e + t.size, 0), d = () => {
|
|
285
272
|
let e = u;
|
|
286
|
-
for (let t of
|
|
273
|
+
for (let t of s.values()) e += t;
|
|
287
274
|
i.onProgress?.({
|
|
288
275
|
phase: "uploading",
|
|
289
276
|
loaded: e,
|
|
@@ -312,7 +299,7 @@ var u = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, d =
|
|
|
312
299
|
}, x = async (t) => {
|
|
313
300
|
let r = e.slice(t.offset, t.offset + t.size);
|
|
314
301
|
await this.retry(async () => {
|
|
315
|
-
t.sha256 || (t.sha256 = await
|
|
302
|
+
t.sha256 || (t.sha256 = await c(r, { signal: g }), o());
|
|
316
303
|
let { parts: e } = await this.api.parts(n.ref, {
|
|
317
304
|
ticket: n.ticket,
|
|
318
305
|
parts: [{
|
|
@@ -322,18 +309,18 @@ var u = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, d =
|
|
|
322
309
|
}]
|
|
323
310
|
}, g), i = e[0]?.request;
|
|
324
311
|
await y();
|
|
325
|
-
let
|
|
312
|
+
let l = performance.now(), f = _;
|
|
326
313
|
try {
|
|
327
314
|
await this.transport(i, r, {
|
|
328
315
|
signal: g,
|
|
329
316
|
onProgress: (e) => {
|
|
330
|
-
|
|
317
|
+
s.set(t.number, e), d();
|
|
331
318
|
}
|
|
332
319
|
});
|
|
333
320
|
} finally {
|
|
334
|
-
|
|
321
|
+
s.delete(t.number), b();
|
|
335
322
|
}
|
|
336
|
-
a.record(t.size, performance.now() -
|
|
323
|
+
a.record(t.size, performance.now() - l, f), u += t.size, d();
|
|
337
324
|
}, g);
|
|
338
325
|
};
|
|
339
326
|
o(), d();
|
|
@@ -376,21 +363,21 @@ var u = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, d =
|
|
|
376
363
|
let o = i(e);
|
|
377
364
|
if (n?.aborted) throw t(n);
|
|
378
365
|
if (!o.transient || a >= this.retries) throw o;
|
|
379
|
-
await
|
|
366
|
+
await _(this.delay(a, o.retryAfter), n), await r?.(o);
|
|
380
367
|
}
|
|
381
368
|
}
|
|
382
369
|
};
|
|
383
|
-
function
|
|
370
|
+
function h(e) {
|
|
384
371
|
return {
|
|
385
372
|
name: e.name,
|
|
386
373
|
lastModified: e.lastModified,
|
|
387
374
|
size: e.size
|
|
388
375
|
};
|
|
389
376
|
}
|
|
390
|
-
function
|
|
377
|
+
function g(e, t) {
|
|
391
378
|
return t ? t * 1e3 : Math.min(3e4, 1e3 * 2 ** e) * (.75 + Math.random() * .5);
|
|
392
379
|
}
|
|
393
|
-
function
|
|
380
|
+
function _(e, n) {
|
|
394
381
|
return new Promise((r, i) => {
|
|
395
382
|
if (n?.aborted) return i(t(n));
|
|
396
383
|
let a = setTimeout(() => {
|
|
@@ -402,6 +389,6 @@ function g(e, n) {
|
|
|
402
389
|
});
|
|
403
390
|
}
|
|
404
391
|
//#endregion
|
|
405
|
-
export {
|
|
392
|
+
export { m as MediaClient, g as backoff, f as samePath, _ as sleep, p as stem };
|
|
406
393
|
|
|
407
394
|
//# sourceMappingURL=client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":[],"sources":["../../../src/client/media/client.ts"],"sourcesContent":["import { ContentKitError, aborted, failureError, throwIfAborted, toContentKitError } from \"../errors.js\";\nimport type { CommitBody, Edit, FileInfo, InlineImage, Op, PresetRule, PresignReply, ReadResult, RefBody, RequestReply } from \"../generated/wire.js\";\nimport type { Http } from \"../http.js\";\nimport { call } from \"../route.js\";\nimport { MediaApi, type ReadOptions } from \"./api.js\";\nimport { sha256Hex } from \"./hash.js\";\nimport { Pacer } from \"./pacer.js\";\nimport { defaultTransport, type Transport } from \"./transport.js\";\n\n/** Upload tuning for the media module. */\nexport interface MediaOptions {\n /** Sends presigned PUTs. Default XMLHttpRequest (upload progress), else fetch. */\n transport?: Transport;\n /** Parallel parts per multipart upload (the pacer may run fewer). Default 4. */\n concurrency?: number;\n /** Retries per part and per idempotent API call. Default 5. */\n retries?: number;\n /** Delay before retry attempt (0-based); retryAfter is the server's hint in seconds. */\n retryDelay?: (attempt: number, retryAfter?: number) => number;\n /** Seconds one part should take. Default 30. */\n targetPartSeconds?: number;\n}\n\nexport interface Progress {\n /** processing: committed, waiting for the worker (put). */\n phase: \"hashing\" | \"uploading\" | \"completing\" | \"processing\";\n loaded: number;\n total: number;\n}\n\nexport interface UploadOptions {\n ref: RefBody;\n /** The upload path: a literal (\"cover\") or under a pattern (\"originals/001.png\"); the server cleans it and adds the extension. */\n path: string;\n /** Content type; default the file's. */\n type?: string;\n /** Aborting pauses: a multipart upload stays resumable from the last state. */\n signal?: AbortSignal;\n onProgress?: (p: Progress) => void;\n /** State saved from onState, to resume a multipart upload of the same file. */\n resume?: UploadState;\n /** Resumable state after every change; null once the upload completed. Persist it to survive a reload. */\n onState?: (s: UploadState | null) => void;\n}\n\n/** An upload, ready for a put op. */\nexport interface UploadedFile {\n /** The path to commit: cleaned, with an extension, named by the server for a Named upload. */\n path: string;\n /**\n * The name to commit: the folder's identical blob (\"sha256-{hex}-{uuid}\") when\n * exists, else the staged upload (\"u-{uuid}\") the worker hashes and places\n * after the commit.\n */\n blob: string;\n type: string;\n size: number;\n /** The identical file was already in the item's folder; nothing was sent. */\n exists: boolean;\n /** The host processes files on upload: commit it unattached now (the queue does). */\n processOnUpload?: boolean;\n}\n\n/** The put op's fields besides path and blob. */\nexport interface PutOptions {\n /** Refuse to replace an existing upload. Default false. */\n createOnly?: boolean;\n edit?: Edit | null;\n meta?: Record<string, unknown>;\n index?: number;\n /** Wait until the worker processed the upload (waitFor). Default true. */\n wait?: boolean;\n /** How long to wait, ms. */\n timeout?: number;\n /** The upload, before the commit. */\n onUploaded?: (up: UploadedFile) => void;\n}\n\nexport interface WaitOptions {\n signal?: AbortSignal;\n /** Poll interval, ms. Default 1000. */\n interval?: number;\n /** ms; then render_timeout. Default 120000. */\n timeout?: number;\n}\n\nexport interface PlannedPart {\n number: number;\n offset: number;\n size: number;\n sha256?: string;\n}\n\n/** A multipart upload in flight; JSON-serializable. */\nexport interface UploadState {\n ticket: string;\n path: string;\n /** The staged upload (\"u-{uuid}\") the parts write. */\n blob: string;\n type: string;\n size: number;\n ref: RefBody;\n file: { name?: string; lastModified?: number; size: number };\n limits: { minPartSize: number; maxPartSize: number; maxParts: number };\n parts: PlannedPart[];\n processOnUpload?: boolean;\n}\n\n/** An upload to a server-named (Named) path: a post's or poll's image. */\nexport interface NamedOptions {\n /** The requested path; the server names the file. Default the file's name. */\n path?: string;\n signal?: AbortSignal;\n onProgress?: (p: Progress) => void;\n /** Wait until the worker processed it. Default true. */\n wait?: boolean;\n /** How long to wait, ms. */\n timeout?: number;\n}\n\n/** An upload to a Named path, committed. */\nexport interface NamedUpload {\n /** The server's name for it (\"i-{uuid}\"), which content routes take. */\n name: string;\n path: string;\n file: FileInfo;\n}\n\n/** The media kinds of post and poll folders (content.Media.PostKind and PollKind). */\nexport interface ContentFolders {\n /** Default \"post\". */\n post?: string;\n /** Default \"poll\". */\n poll?: string;\n}\n\nconst named = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;\n\n/** A file to upload again when its blob is gone at commit; type defaults to the file's. */\nexport type CommitSource = Blob | { file: Blob; type?: string };\n\nexport interface CommitOptions {\n signal?: AbortSignal;\n /** UUID for this complete batch. Keep it with unchanged ops for a later retry; default a new UUID. */\n operationID?: string;\n /** Persist the active batch before sending it, including any replacement after re-upload. Resume with its ops and operation_id. */\n onState?: (batch: CommitBody) => void;\n /** Blob (\"sha256-…\") → the file it was uploaded from. */\n sources?: Record<string, CommitSource>;\n}\n\ntype Uploadable = Blob & { name?: string; lastModified?: number };\n\n/** A path names a file by its full path or, for an upload, its stem (\"cover\" for \"cover.png\"). */\nexport const samePath = (file: string, path: string) => file === path || stem(file) === path;\n\n/** The path without its extension. */\nexport const stem = (path: string) => {\n const i = path.lastIndexOf(\".\");\n return i > path.lastIndexOf(\"/\") + 1 ? path.slice(0, i) : path;\n};\n\n/** The media module: uploads, commits, reads, frames and HLS. */\nexport class MediaClient {\n readonly api: MediaApi;\n private readonly transport: Transport;\n private readonly concurrency: number;\n private readonly retries: number;\n private readonly delay: (attempt: number, retryAfter?: number) => number;\n private readonly targetSeconds: number;\n private presetList?: Promise<PresetRule[]>;\n\n private readonly folders: Required<ContentFolders>;\n\n constructor(\n private readonly http: Http,\n o: MediaOptions = {},\n folders: ContentFolders = {},\n ) {\n this.folders = { post: folders.post ?? \"post\", poll: folders.poll ?? \"poll\" };\n this.api = new MediaApi(http);\n this.transport = o.transport ?? defaultTransport;\n this.concurrency = o.concurrency ?? 4;\n this.retries = o.retries ?? 5;\n this.delay = o.retryDelay ?? backoff;\n this.targetSeconds = o.targetPartSeconds ?? 30;\n }\n\n /**\n * Uploads file to the item's folder: it hashes the whole file (off the\n * main thread) and presigns {path, type, size, sha256}, then sends nothing\n * (the folder holds the identical blob), or stages it: one checksum-bound\n * PUT up to 64 MiB, resumable multipart above. A refused presign throws\n * before any bytes move. Commit the result with a put op (or use put());\n * the worker then places a staged upload at the hash of its bytes.\n */\n async upload(file: Uploadable, o: UploadOptions): Promise<UploadedFile> {\n const type = o.type || file.type;\n if (!type) throw new ContentKitError(\"invalid_request\", \"the file has no content type\");\n throwIfAborted(o.signal);\n if (o.resume) return this.resumeMultipart(file, o.resume, o);\n const total = file.size;\n const sha256 = await sha256Hex(file, { signal: o.signal, onProgress: (loaded) => o.onProgress?.({ phase: \"hashing\", loaded, total }) });\n const presign = () => this.api.presign({ ref: o.ref, path: o.path, type, size: total, sha256 }, o.signal);\n let p = await presign();\n const out = (p: PresignReply): UploadedFile => ({ path: p.path, blob: p.blob, type, size: total, exists: !!p.exists, processOnUpload: !!p.process_on_upload });\n if (p.exists) return out(p);\n if (p.multipart) {\n const m = p.multipart;\n const state: UploadState = {\n ticket: m.ticket,\n path: p.path,\n blob: p.blob,\n type,\n size: total,\n ref: o.ref,\n file: fingerprint(file),\n limits: { minPartSize: m.min_part_size, maxPartSize: m.max_part_size, maxParts: m.max_parts },\n parts: [],\n processOnUpload: !!p.process_on_upload,\n };\n return this.multipart(file, state, new Set(), o);\n }\n await this.retry(\n async () => {\n if (p.exists) return;\n await this.transport(p.put!, file, { signal: o.signal, onProgress: (loaded) => o.onProgress?.({ phase: \"uploading\", loaded, total }) });\n },\n o.signal,\n async (err) => {\n // An expired URL: presign again (a Named upload gets a new name).\n if (err.code === \"storage\" && err.status === 403) p = await presign();\n },\n );\n return out(p);\n }\n\n /**\n * Uploads file and commits it as a put to its path (with edit, meta and\n * index), then by default waits until the worker processed it; resolves\n * with the upload as an editor reads it.\n */\n async put(file: Uploadable, o: UploadOptions & PutOptions): Promise<FileInfo> {\n const up = await this.upload(file, o);\n o.onUploaded?.(up);\n const op: Op = { op: \"put\", path: up.path, blob: up.blob };\n if (o.createOnly) op.create_id = crypto.randomUUID();\n if (o.edit) op.edit = o.edit;\n if (o.meta) op.meta = o.meta;\n if (o.index !== undefined) op.index = o.index;\n const files = await this.commit(o.ref, [op], { signal: o.signal, sources: { [up.blob]: { file, type: up.type } } });\n if (o.wait === false) return files.find((f) => f.path === up.path) ?? { path: up.path, type: up.type };\n o.onProgress?.({ phase: \"processing\", loaded: up.size, total: up.size });\n return this.waitFor(o.ref, up.path, { signal: o.signal, timeout: o.timeout });\n }\n\n /**\n * Applies ops to the item in one conditional write; returns its uploads as\n * an editor reads them. With sources (blob → the file uploaded as it), a\n * not_uploaded refusal (the blob expired or is due for cleanup) uploads\n * the affected files again and retries the commit once.\n */\n async commit(ref: RefBody, ops: Op[], o: CommitOptions = {}): Promise<FileInfo[]> {\n const emit = this.http.captureChanges();\n // Snapshot the batch once: retries must not pick up edits to the caller's\n // ops while the first request or its response is still in flight.\n const body: CommitBody = structuredClone({ ref, ops, operation_id: o.operationID ?? crypto.randomUUID() });\n o.onState?.(structuredClone(body));\n let files: FileInfo[];\n try {\n files = (await this.retry(() => this.api.commit(body, o.signal), o.signal)).files;\n } catch (e) {\n if (!(e instanceof ContentKitError) || e.code !== \"not_uploaded\") throw e;\n const sources = o.sources ?? {};\n const puts = body.ops.filter((op) => op.op === \"put\" && op.blob && op.blob in sources);\n const stale = [...new Set(puts.map((op) => op.blob!))].filter((b) => !e.blobs || e.blobs.includes(b));\n if (stale.length === 0) throw e;\n const renamed = new Map<string, string>();\n for (const blob of stale) {\n const src = sources[blob]!;\n const file = src instanceof Blob ? src : src.file;\n const type = src instanceof Blob ? undefined : src.type;\n const op = puts.find((p) => p.blob === blob)!;\n renamed.set(blob, (await this.upload(file, { ref: body.ref, path: op.path!, type, signal: o.signal })).blob);\n }\n const retried = body.ops.map((op) => (op.blob && renamed.has(op.blob) ? { ...op, blob: renamed.get(op.blob) } : op));\n // Re-uploading changes the inputs. This is a new batch, not a retry\n // under the old ID; transport retries retain its new identity.\n const replacement = { ref: body.ref, ops: retried, operation_id: crypto.randomUUID() };\n o.onState?.(structuredClone(replacement));\n files = (await this.retry(() => this.api.commit(replacement, o.signal), o.signal)).files;\n }\n emit({ type: \"media.committed\", ref: { kind: ref.kind, id: ref.id }, files });\n return files;\n }\n\n /** The read API: the item's files under prefix in manifest order, with signed URLs for what this viewer may have. */\n async read(ref: RefBody, o: ReadOptions & { signal?: AbortSignal } = {}): Promise<ReadResult> {\n this.api.itemURL(ref); // a bad ref fails at once\n return this.retry(() => this.api.read(ref, o, o.signal), o.signal);\n }\n\n /**\n * Polls an editor read until the upload at path (or with that stem) is\n * processed: placed (not staged), nothing pending and its blob present (a\n * frame grabbed).\n * Rejects with its failure, not_found once it is gone, too_large while the\n * item is full (nothing is processed until uploads are removed),\n * render_timeout after the timeout.\n */\n async waitFor(ref: RefBody, path: string, o: WaitOptions = {}): Promise<FileInfo> {\n const emit = this.http.captureChanges();\n const until = Date.now() + (o.timeout ?? 120_000);\n for (;;) {\n const r = await this.read(ref, { editor: true, prefix: stem(path), signal: o.signal });\n const f = r.files.find((x) => x.upload && samePath(x.path, path));\n if (!f) throw new ContentKitError(\"not_found\", `no upload ${path}`, { status: 404 });\n if (f.failed) throw failureError(f.failed);\n if (!f.pending?.length && !f.staged && (f.size ?? 0) > 0) {\n emit({ type: \"media.processed\", ref: { kind: ref.kind, id: ref.id }, file: f });\n return f;\n }\n if (r.full) throw new ContentKitError(\"too_large\", `${path} waits: the item is full; remove uploads to process more`, { status: 413 });\n if (Date.now() >= until) throw new ContentKitError(\"render_timeout\", `${path} is still processing`);\n await sleep(o.interval ?? 1000, o.signal);\n }\n }\n\n /**\n * The upload's editor view, for re-cropping: its URL and the upload's\n * oriented size. Waits while it renders (an editor read asks for it);\n * not_found when the path has no upload, render_timeout after the timeout\n * (default 30 s).\n */\n async editorView(ref: RefBody, path: string, o: WaitOptions = {}): Promise<{ url: string; width: number; height: number; edit?: Edit }> {\n const until = Date.now() + (o.timeout ?? 30_000);\n for (;;) {\n const r = await this.read(ref, { editor: true, prefix: stem(path), signal: o.signal });\n const f = r.files.find((x) => x.upload && samePath(x.path, path));\n if (!f) throw new ContentKitError(\"not_found\", `no upload ${path}`, { status: 404 });\n if (f.editor_url && f.w && f.h) return { url: f.editor_url, width: f.w, height: f.h, ...(f.edit ? { edit: f.edit } : {}) };\n if (f.failed) throw failureError(f.failed);\n if (Date.now() >= until) throw new ContentKitError(\"render_timeout\", `the editor view of ${path} is still rendering`);\n await sleep(o.interval ?? 1000, o.signal);\n }\n }\n\n /**\n * Every kind's public presets, fetched once per client: crop bounds,\n * widths and default images. An item's current image is in its read's\n * `public`, never built from a preset. Shared by every caller, so it takes\n * no signal; a failed fetch is retried on the next call.\n */\n presets(): Promise<PresetRule[]> {\n this.presetList ??= this.retry(() => this.api.presets()).catch((e: unknown) => {\n this.presetList = undefined;\n throw e;\n });\n return this.presetList;\n }\n\n /** A JPEG still of the video upload at path, t seconds in, w pixels wide (default the frame's). */\n getFrame(ref: RefBody, path: string, t: number, w?: number, signal?: AbortSignal): Promise<Blob> {\n return this.retry(() => this.api.frame(ref, path, t, w, signal), signal);\n }\n\n /** The folder of an HLS ladder from a read's `hls`: `{media}/{kind}/{id}/hls/{dir}`; master.m3u8 and sprite.vtt resolve under it. */\n hlsBase(ref: RefBody, dir: string): string {\n return `${this.api.itemURL(ref)}/hls/${dir}`;\n }\n\n /** hls.js `xhrSetup` for playlists on the API (the client's token and credentials); pass it to players as is. */\n get xhrSetup(): (xhr: XMLHttpRequest, url: string) => Promise<void> {\n return this.http.xhrSetup;\n }\n\n /**\n * Uploads an image to the item's server-named upload path and commits it\n * (by default waiting until it is processed); resolves with the name content\n * routes take: a poll or option image, a post cover.\n */\n async uploadNamed(ref: RefBody, file: Uploadable, o: NamedOptions = {}): Promise<NamedUpload> {\n const f = await this.put(file, { ref, path: o.path ?? (file.name || \"image\"), signal: o.signal, onProgress: o.onProgress, wait: o.wait, timeout: o.timeout });\n const name = stem(f.path.slice(f.path.lastIndexOf(\"/\") + 1));\n if (!named.test(name)) throw new ContentKitError(\"invalid_request\", `${f.path} is not a server-named upload path of ${ref.kind}`);\n return { name, path: f.path, file: f };\n }\n\n /**\n * An image for a post's rich-text body, uploaded to the post's folder;\n * resolves with its reference to store in the body (`ref`) and a URL that\n * shows it to the editor now (`url`, null while none exists).\n */\n async uploadInline(post: string, file: Uploadable, o: NamedOptions = {}): Promise<NamedUpload & InlineImage> {\n const up = await this.uploadNamed(this.postRef(post), file, o);\n const r: InlineImage = await call(this.http, \"POST\", \"/posts/{id}/images\", { params: { id: post }, body: { image: up.name }, signal: o.signal });\n return { ...up, ...r };\n }\n\n /** The media folder of a post. */\n postRef(id: string): RefBody {\n return { kind: this.folders.post, id };\n }\n\n /** The media folder of a poll. */\n pollRef(id: string): RefBody {\n return { kind: this.folders.poll, id };\n }\n\n /** Discards a paused multipart upload. */\n async discard(state: UploadState): Promise<void> {\n await this.api.abort(state.ref, { ticket: state.ticket });\n }\n\n private async resumeMultipart(file: Uploadable, state: UploadState, o: UploadOptions): Promise<UploadedFile> {\n const fp = fingerprint(file);\n if (fp.size !== state.size || fp.name !== state.file.name || fp.lastModified !== state.file.lastModified) {\n throw new ContentKitError(\"resume_mismatch\", \"the saved upload is for a different file\");\n }\n let listed;\n try {\n listed = await this.retry(() => this.api.listParts(state.ref, { ticket: state.ticket }, o.signal), o.signal);\n } catch (err) {\n // Gone: already completed (Complete is idempotent) or expired.\n if (err instanceof ContentKitError && err.code === \"not_found\") return this.complete(state, o);\n throw err;\n }\n const landed = new Set<number>();\n for (const l of listed.parts) {\n const plan = state.parts.find((p) => p.number === l.number);\n if (!plan || plan.size !== l.size || (l.sha256 && plan.sha256 !== l.sha256)) {\n // Parts this state does not describe: the upload cannot be finished consistently.\n await this.discard(state).catch(() => {});\n o.onState?.(null);\n return this.upload(file, { ...o, resume: undefined });\n }\n landed.add(l.number);\n }\n return this.multipart(file, structuredClone(state), landed, o);\n }\n\n private async multipart(file: Uploadable, state: UploadState, landed: Set<number>, o: UploadOptions): Promise<UploadedFile> {\n const pacer = new Pacer({ ...state.limits, maxConcurrency: this.concurrency, targetSeconds: this.targetSeconds });\n const emitState = () => o.onState?.(structuredClone(state));\n const sending = new Map<number, number>();\n let sent = state.parts.filter((p) => landed.has(p.number)).reduce((n, p) => n + p.size, 0);\n const emitProgress = () => {\n let loaded = sent;\n for (const n of sending.values()) loaded += n;\n o.onProgress?.({ phase: \"uploading\", loaded, total: state.size });\n };\n const pending = state.parts.filter((p) => !landed.has(p.number));\n let offset = state.parts.reduce((end, p) => Math.max(end, p.offset + p.size), 0);\n const next = (): PlannedPart | undefined => {\n const p = pending.shift();\n if (p || offset >= state.size) return p;\n const part: PlannedPart = {\n number: state.parts.length + 1,\n offset,\n size: Math.min(pacer.partSize(), state.size - offset),\n };\n offset += part.size;\n state.parts.push(part);\n return part;\n };\n\n const stop = new AbortController();\n const signal = o.signal ? AbortSignal.any([o.signal, stop.signal]) : stop.signal;\n // Parts hash and presign ahead of the PUT slots, so a freed slot starts\n // its next PUT at once; pacer.concurrency bounds only the PUTs.\n let putting = 0;\n const waiting: (() => void)[] = [];\n const acquire = () =>\n new Promise<void>((resolve, reject) => {\n const take = () => {\n signal.removeEventListener(\"abort\", drop);\n putting++;\n resolve();\n };\n const drop = () => {\n waiting.splice(waiting.indexOf(take) >>> 0, 1);\n reject(aborted(signal));\n };\n if (putting < pacer.concurrency) return take();\n if (signal.aborted) return reject(aborted(signal));\n signal.addEventListener(\"abort\", drop, { once: true });\n waiting.push(take);\n });\n const release = () => {\n putting--;\n while (putting < pacer.concurrency && waiting.length) waiting.shift()!();\n };\n const run = async (part: PlannedPart) => {\n const body = file.slice(part.offset, part.offset + part.size);\n await this.retry(async () => {\n if (!part.sha256) {\n part.sha256 = await sha256Hex(body, { signal });\n emitState();\n }\n const { parts } = await this.api.parts(\n state.ref,\n { ticket: state.ticket, parts: [{ number: part.number, size: part.size, sha256: part.sha256 }] },\n signal,\n );\n const req = parts[0]?.request as RequestReply;\n await acquire();\n const began = performance.now();\n const alongside = putting;\n try {\n await this.transport(req, body, {\n signal,\n onProgress: (n) => {\n sending.set(part.number, n);\n emitProgress();\n },\n });\n } finally {\n sending.delete(part.number);\n release();\n }\n pacer.record(part.size, performance.now() - began, alongside);\n sent += part.size;\n emitProgress();\n }, signal);\n };\n\n emitState();\n emitProgress();\n const inFlight = new Set<Promise<void>>();\n let failure: unknown;\n for (;;) {\n let part: PlannedPart | undefined;\n while (failure === undefined && inFlight.size < pacer.concurrency + 2 && (part = next())) {\n emitState();\n const job: Promise<void> = run(part).then(\n () => void inFlight.delete(job),\n (err) => {\n inFlight.delete(job);\n if (failure === undefined) {\n failure = err;\n stop.abort();\n }\n },\n );\n inFlight.add(job);\n }\n if (inFlight.size === 0) break;\n await Promise.race(inFlight);\n }\n if (failure !== undefined) {\n throw o.signal?.aborted ? aborted(o.signal) : failure;\n }\n return this.complete(state, o);\n }\n\n private async complete(state: UploadState, o: UploadOptions): Promise<UploadedFile> {\n o.onProgress?.({ phase: \"completing\", loaded: state.size, total: state.size });\n const c = await this.retry(() => this.api.complete(state.ref, { ticket: state.ticket }, o.signal), o.signal);\n o.onState?.(null);\n return { path: state.path, blob: c.blob, type: c.type, size: c.size, exists: false, processOnUpload: state.processOnUpload };\n }\n\n private async retry<T>(fn: () => Promise<T>, signal?: AbortSignal, before?: (err: ContentKitError) => Promise<void>): Promise<T> {\n for (let attempt = 0; ; attempt++) {\n try {\n return await fn();\n } catch (e) {\n const err = toContentKitError(e);\n if (signal?.aborted) throw aborted(signal);\n if (!err.transient || attempt >= this.retries) throw err;\n await sleep(this.delay(attempt, err.retryAfter), signal);\n await before?.(err);\n }\n }\n }\n}\n\nfunction fingerprint(f: Uploadable): UploadState[\"file\"] {\n return { name: f.name, lastModified: f.lastModified, size: f.size };\n}\n\n/** Exponential backoff from 1 s to 30 s with jitter, or the server's Retry-After. */\nexport function backoff(attempt: number, retryAfter?: number): number {\n if (retryAfter) return retryAfter * 1000;\n const base = Math.min(30_000, 1000 * 2 ** attempt);\n return base * (0.75 + Math.random() * 0.5);\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) return reject(aborted(signal));\n const t = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(t);\n reject(aborted(signal));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n"],"mappings":";;;;;;;AAwIA,IAAM,IAAQ,oEAkBD,KAAY,GAAc,MAAiB,MAAS,KAAQ,EAAK,CAAI,MAAM,GAG3E,KAAQ,MAAiB;CACpC,IAAM,IAAI,EAAK,YAAY,GAAG;CAC9B,OAAO,IAAI,EAAK,YAAY,GAAG,IAAI,IAAI,EAAK,MAAM,GAAG,CAAC,IAAI;AAC5D,GAGa,IAAb,MAAyB;CAYJ;CAXnB;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CAEA,YACE,GACA,IAAkB,CAAC,GACnB,IAA0B,CAAC,GAC3B;EAOA,AAViB,KAAA,OAAA,GAIjB,KAAK,UAAU;GAAE,MAAM,EAAQ,QAAQ;GAAQ,MAAM,EAAQ,QAAQ;EAAO,GAC5E,KAAK,MAAM,IAAI,EAAS,CAAI,GAC5B,KAAK,YAAY,EAAE,aAAa,GAChC,KAAK,cAAc,EAAE,eAAe,GACpC,KAAK,UAAU,EAAE,WAAW,GAC5B,KAAK,QAAQ,EAAE,cAAc,GAC7B,KAAK,gBAAgB,EAAE,qBAAqB;CAC9C;CAUA,MAAM,OAAO,GAAkB,GAAyC;EACtE,IAAM,IAAO,EAAE,QAAQ,EAAK;EAC5B,IAAI,CAAC,GAAM,MAAM,IAAI,EAAgB,mBAAmB,8BAA8B;EAEtF,IADA,EAAe,EAAE,MAAM,GACnB,EAAE,QAAQ,OAAO,KAAK,gBAAgB,GAAM,EAAE,QAAQ,CAAC;EAC3D,IAAM,IAAQ,EAAK,MACb,IAAS,MAAM,EAAU,GAAM;GAAE,QAAQ,EAAE;GAAQ,aAAa,MAAW,EAAE,aAAa;IAAE,OAAO;IAAW;IAAQ;GAAM,CAAC;EAAE,CAAC,GAChI,UAAgB,KAAK,IAAI,QAAQ;GAAE,KAAK,EAAE;GAAK,MAAM,EAAE;GAAM;GAAM,MAAM;GAAO;EAAO,GAAG,EAAE,MAAM,GACpG,IAAI,MAAM,EAAQ,GAChB,KAAO,OAAmC;GAAE,MAAM,EAAE;GAAM,MAAM,EAAE;GAAM;GAAM,MAAM;GAAO,QAAQ,CAAC,CAAC,EAAE;GAAQ,iBAAiB,CAAC,CAAC,EAAE;EAAkB;EAC5J,IAAI,EAAE,QAAQ,OAAO,EAAI,CAAC;EAC1B,IAAI,EAAE,WAAW;GACf,IAAM,IAAI,EAAE,WACN,IAAqB;IACzB,QAAQ,EAAE;IACV,MAAM,EAAE;IACR,MAAM,EAAE;IACR;IACA,MAAM;IACN,KAAK,EAAE;IACP,MAAM,EAAY,CAAI;IACtB,QAAQ;KAAE,aAAa,EAAE;KAAe,aAAa,EAAE;KAAe,UAAU,EAAE;IAAU;IAC5F,OAAO,CAAC;IACR,iBAAiB,CAAC,CAAC,EAAE;GACvB;GACA,OAAO,KAAK,UAAU,GAAM,mBAAO,IAAI,IAAI,GAAG,CAAC;EACjD;EAYA,OAXA,MAAM,KAAK,MACT,YAAY;GACN,EAAE,UACN,MAAM,KAAK,UAAU,EAAE,KAAM,GAAM;IAAE,QAAQ,EAAE;IAAQ,aAAa,MAAW,EAAE,aAAa;KAAE,OAAO;KAAa;KAAQ;IAAM,CAAC;GAAE,CAAC;EACxI,GACA,EAAE,QACF,OAAO,MAAQ;GAEb,AAAI,EAAI,SAAS,aAAa,EAAI,WAAW,QAAK,IAAI,MAAM,EAAQ;EACtE,CACF,GACO,EAAI,CAAC;CACd;CAOA,MAAM,IAAI,GAAkB,GAAkD;EAC5E,IAAM,IAAK,MAAM,KAAK,OAAO,GAAM,CAAC;EACpC,EAAE,aAAa,CAAE;EACjB,IAAM,IAAS;GAAE,IAAI;GAAO,MAAM,EAAG;GAAM,MAAM,EAAG;EAAK;EAIzD,AAHI,EAAE,eAAY,EAAG,YAAY,OAAO,WAAW,IAC/C,EAAE,SAAM,EAAG,OAAO,EAAE,OACpB,EAAE,SAAM,EAAG,OAAO,EAAE,OACpB,EAAE,UAAU,KAAA,MAAW,EAAG,QAAQ,EAAE;EACxC,IAAM,IAAQ,MAAM,KAAK,OAAO,EAAE,KAAK,CAAC,CAAE,GAAG;GAAE,QAAQ,EAAE;GAAQ,SAAS,GAAG,EAAG,OAAO;IAAE;IAAM,MAAM,EAAG;GAAK,EAAE;EAAE,CAAC;EAGlH,OAFI,EAAE,SAAS,KAAc,EAAM,MAAM,MAAM,EAAE,SAAS,EAAG,IAAI,KAAK;GAAE,MAAM,EAAG;GAAM,MAAM,EAAG;EAAK,KACrG,EAAE,aAAa;GAAE,OAAO;GAAc,QAAQ,EAAG;GAAM,OAAO,EAAG;EAAK,CAAC,GAChE,KAAK,QAAQ,EAAE,KAAK,EAAG,MAAM;GAAE,QAAQ,EAAE;GAAQ,SAAS,EAAE;EAAQ,CAAC;CAC9E;CAQA,MAAM,OAAO,GAAc,GAAW,IAAmB,CAAC,GAAwB;EAChF,IAAM,IAAO,KAAK,KAAK,eAAe,GAGhC,IAAmB,gBAAgB;GAAE;GAAK;GAAK,cAAc,EAAE,eAAe,OAAO,WAAW;EAAE,CAAC;EACzG,EAAE,UAAU,gBAAgB,CAAI,CAAC;EACjC,IAAI;EACJ,IAAI;GACF,KAAS,MAAM,KAAK,YAAY,KAAK,IAAI,OAAO,GAAM,EAAE,MAAM,GAAG,EAAE,MAAM,EAAA,CAAG;EAC9E,SAAS,GAAG;GACV,IAAI,EAAE,aAAa,MAAoB,EAAE,SAAS,gBAAgB,MAAM;GACxE,IAAM,IAAU,EAAE,WAAW,CAAC,GACxB,IAAO,EAAK,IAAI,QAAQ,MAAO,EAAG,OAAO,SAAS,EAAG,QAAQ,EAAG,QAAQ,CAAO,GAC/E,IAAQ,CAAC,GAAG,IAAI,IAAI,EAAK,KAAK,MAAO,EAAG,IAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC,CAAC;GACpG,IAAI,EAAM,WAAW,GAAG,MAAM;GAC9B,IAAM,oBAAU,IAAI,IAAoB;GACxC,KAAK,IAAM,KAAQ,GAAO;IACxB,IAAM,IAAM,EAAQ,IACd,IAAO,aAAe,OAAO,IAAM,EAAI,MACvC,IAAO,aAAe,OAAO,KAAA,IAAY,EAAI,MAC7C,IAAK,EAAK,MAAM,MAAM,EAAE,SAAS,CAAI;IAC3C,EAAQ,IAAI,IAAO,MAAM,KAAK,OAAO,GAAM;KAAE,KAAK,EAAK;KAAK,MAAM,EAAG;KAAO;KAAM,QAAQ,EAAE;IAAO,CAAC,EAAA,CAAG,IAAI;GAC7G;GACA,IAAM,IAAU,EAAK,IAAI,KAAK,MAAQ,EAAG,QAAQ,EAAQ,IAAI,EAAG,IAAI,IAAI;IAAE,GAAG;IAAI,MAAM,EAAQ,IAAI,EAAG,IAAI;GAAE,IAAI,CAAG,GAG7G,IAAc;IAAE,KAAK,EAAK;IAAK,KAAK;IAAS,cAAc,OAAO,WAAW;GAAE;GAErF,AADA,EAAE,UAAU,gBAAgB,CAAW,CAAC,GACxC,KAAS,MAAM,KAAK,YAAY,KAAK,IAAI,OAAO,GAAa,EAAE,MAAM,GAAG,EAAE,MAAM,EAAA,CAAG;EACrF;EAEA,OADA,EAAK;GAAE,MAAM;GAAmB,KAAK;IAAE,MAAM,EAAI;IAAM,IAAI,EAAI;GAAG;GAAG;EAAM,CAAC,GACrE;CACT;CAGA,MAAM,KAAK,GAAc,IAA4C,CAAC,GAAwB;EAE5F,OADA,KAAK,IAAI,QAAQ,CAAG,GACb,KAAK,YAAY,KAAK,IAAI,KAAK,GAAK,GAAG,EAAE,MAAM,GAAG,EAAE,MAAM;CACnE;CAUA,MAAM,QAAQ,GAAc,GAAc,IAAiB,CAAC,GAAsB;EAChF,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAQ,KAAK,IAAI,KAAK,EAAE,WAAW;EACzC,SAAS;GACP,IAAM,IAAI,MAAM,KAAK,KAAK,GAAK;IAAE,QAAQ;IAAM,QAAQ,EAAK,CAAI;IAAG,QAAQ,EAAE;GAAO,CAAC,GAC/E,IAAI,EAAE,MAAM,MAAM,MAAM,EAAE,UAAU,EAAS,EAAE,MAAM,CAAI,CAAC;GAChE,IAAI,CAAC,GAAG,MAAM,IAAI,EAAgB,aAAa,aAAa,KAAQ,EAAE,QAAQ,IAAI,CAAC;GACnF,IAAI,EAAE,QAAQ,MAAM,EAAa,EAAE,MAAM;GACzC,IAAI,CAAC,EAAE,SAAS,UAAU,CAAC,EAAE,WAAW,EAAE,QAAQ,KAAK,GAErD,OADA,EAAK;IAAE,MAAM;IAAmB,KAAK;KAAE,MAAM,EAAI;KAAM,IAAI,EAAI;IAAG;IAAG,MAAM;GAAE,CAAC,GACvE;GAET,IAAI,EAAE,MAAM,MAAM,IAAI,EAAgB,aAAa,GAAG,EAAK,2DAA2D,EAAE,QAAQ,IAAI,CAAC;GACrI,IAAI,KAAK,IAAI,KAAK,GAAO,MAAM,IAAI,EAAgB,kBAAkB,GAAG,EAAK,qBAAqB;GAClG,MAAM,EAAM,EAAE,YAAY,KAAM,EAAE,MAAM;EAC1C;CACF;CAQA,MAAM,WAAW,GAAc,GAAc,IAAiB,CAAC,GAAyE;EACtI,IAAM,IAAQ,KAAK,IAAI,KAAK,EAAE,WAAW;EACzC,SAAS;GAEP,IAAM,KAAI,MADM,KAAK,KAAK,GAAK;IAAE,QAAQ;IAAM,QAAQ,EAAK,CAAI;IAAG,QAAQ,EAAE;GAAO,CAAC,EAAA,CACzE,MAAM,MAAM,MAAM,EAAE,UAAU,EAAS,EAAE,MAAM,CAAI,CAAC;GAChE,IAAI,CAAC,GAAG,MAAM,IAAI,EAAgB,aAAa,aAAa,KAAQ,EAAE,QAAQ,IAAI,CAAC;GACnF,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,GAAG,OAAO;IAAE,KAAK,EAAE;IAAY,OAAO,EAAE;IAAG,QAAQ,EAAE;IAAG,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;GAAG;GACzH,IAAI,EAAE,QAAQ,MAAM,EAAa,EAAE,MAAM;GACzC,IAAI,KAAK,IAAI,KAAK,GAAO,MAAM,IAAI,EAAgB,kBAAkB,sBAAsB,EAAK,oBAAoB;GACpH,MAAM,EAAM,EAAE,YAAY,KAAM,EAAE,MAAM;EAC1C;CACF;CAQA,UAAiC;EAK/B,OAJA,KAAK,eAAe,KAAK,YAAY,KAAK,IAAI,QAAQ,CAAC,CAAC,CAAC,OAAO,MAAe;GAE7E,MADA,KAAK,aAAa,KAAA,GACZ;EACR,CAAC,GACM,KAAK;CACd;CAGA,SAAS,GAAc,GAAc,GAAW,GAAY,GAAqC;EAC/F,OAAO,KAAK,YAAY,KAAK,IAAI,MAAM,GAAK,GAAM,GAAG,GAAG,CAAM,GAAG,CAAM;CACzE;CAGA,QAAQ,GAAc,GAAqB;EACzC,OAAO,GAAG,KAAK,IAAI,QAAQ,CAAG,EAAE,OAAO;CACzC;CAGA,IAAI,WAAgE;EAClE,OAAO,KAAK,KAAK;CACnB;CAOA,MAAM,YAAY,GAAc,GAAkB,IAAkB,CAAC,GAAyB;EAC5F,IAAM,IAAI,MAAM,KAAK,IAAI,GAAM;GAAE;GAAK,MAAM,EAAE,SAAS,EAAK,QAAQ;GAAU,QAAQ,EAAE;GAAQ,YAAY,EAAE;GAAY,MAAM,EAAE;GAAM,SAAS,EAAE;EAAQ,CAAC,GACtJ,IAAO,EAAK,EAAE,KAAK,MAAM,EAAE,KAAK,YAAY,GAAG,IAAI,CAAC,CAAC;EAC3D,IAAI,CAAC,EAAM,KAAK,CAAI,GAAG,MAAM,IAAI,EAAgB,mBAAmB,GAAG,EAAE,KAAK,wCAAwC,EAAI,MAAM;EAChI,OAAO;GAAE;GAAM,MAAM,EAAE;GAAM,MAAM;EAAE;CACvC;CAOA,MAAM,aAAa,GAAc,GAAkB,IAAkB,CAAC,GAAuC;EAC3G,IAAM,IAAK,MAAM,KAAK,YAAY,KAAK,QAAQ,CAAI,GAAG,GAAM,CAAC,GACvD,IAAiB,MAAM,EAAK,KAAK,MAAM,QAAQ,sBAAsB;GAAE,QAAQ,EAAE,IAAI,EAAK;GAAG,MAAM,EAAE,OAAO,EAAG,KAAK;GAAG,QAAQ,EAAE;EAAO,CAAC;EAC/I,OAAO;GAAE,GAAG;GAAI,GAAG;EAAE;CACvB;CAGA,QAAQ,GAAqB;EAC3B,OAAO;GAAE,MAAM,KAAK,QAAQ;GAAM;EAAG;CACvC;CAGA,QAAQ,GAAqB;EAC3B,OAAO;GAAE,MAAM,KAAK,QAAQ;GAAM;EAAG;CACvC;CAGA,MAAM,QAAQ,GAAmC;EAC/C,MAAM,KAAK,IAAI,MAAM,EAAM,KAAK,EAAE,QAAQ,EAAM,OAAO,CAAC;CAC1D;CAEA,MAAc,gBAAgB,GAAkB,GAAoB,GAAyC;EAC3G,IAAM,IAAK,EAAY,CAAI;EAC3B,IAAI,EAAG,SAAS,EAAM,QAAQ,EAAG,SAAS,EAAM,KAAK,QAAQ,EAAG,iBAAiB,EAAM,KAAK,cAC1F,MAAM,IAAI,EAAgB,mBAAmB,0CAA0C;EAEzF,IAAI;EACJ,IAAI;GACF,IAAS,MAAM,KAAK,YAAY,KAAK,IAAI,UAAU,EAAM,KAAK,EAAE,QAAQ,EAAM,OAAO,GAAG,EAAE,MAAM,GAAG,EAAE,MAAM;EAC7G,SAAS,GAAK;GAEZ,IAAI,aAAe,KAAmB,EAAI,SAAS,aAAa,OAAO,KAAK,SAAS,GAAO,CAAC;GAC7F,MAAM;EACR;EACA,IAAM,oBAAS,IAAI,IAAY;EAC/B,KAAK,IAAM,KAAK,EAAO,OAAO;GAC5B,IAAM,IAAO,EAAM,MAAM,MAAM,MAAM,EAAE,WAAW,EAAE,MAAM;GAC1D,IAAI,CAAC,KAAQ,EAAK,SAAS,EAAE,QAAS,EAAE,UAAU,EAAK,WAAW,EAAE,QAIlE,OAFA,MAAM,KAAK,QAAQ,CAAK,CAAC,CAAC,YAAY,CAAC,CAAC,GACxC,EAAE,UAAU,IAAI,GACT,KAAK,OAAO,GAAM;IAAE,GAAG;IAAG,QAAQ,KAAA;GAAU,CAAC;GAEtD,EAAO,IAAI,EAAE,MAAM;EACrB;EACA,OAAO,KAAK,UAAU,GAAM,gBAAgB,CAAK,GAAG,GAAQ,CAAC;CAC/D;CAEA,MAAc,UAAU,GAAkB,GAAoB,GAAqB,GAAyC;EAC1H,IAAM,IAAQ,IAAI,EAAM;GAAE,GAAG,EAAM;GAAQ,gBAAgB,KAAK;GAAa,eAAe,KAAK;EAAc,CAAC,GAC1G,UAAkB,EAAE,UAAU,gBAAgB,CAAK,CAAC,GACpD,oBAAU,IAAI,IAAoB,GACpC,IAAO,EAAM,MAAM,QAAQ,MAAM,EAAO,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,GAAG,MAAM,IAAI,EAAE,MAAM,CAAC,GACnF,UAAqB;GACzB,IAAI,IAAS;GACb,KAAK,IAAM,KAAK,EAAQ,OAAO,GAAG,KAAU;GAC5C,EAAE,aAAa;IAAE,OAAO;IAAa;IAAQ,OAAO,EAAM;GAAK,CAAC;EAClE,GACM,IAAU,EAAM,MAAM,QAAQ,MAAM,CAAC,EAAO,IAAI,EAAE,MAAM,CAAC,GAC3D,IAAS,EAAM,MAAM,QAAQ,GAAK,MAAM,KAAK,IAAI,GAAK,EAAE,SAAS,EAAE,IAAI,GAAG,CAAC,GACzE,UAAsC;GAC1C,IAAM,IAAI,EAAQ,MAAM;GACxB,IAAI,KAAK,KAAU,EAAM,MAAM,OAAO;GACtC,IAAM,IAAoB;IACxB,QAAQ,EAAM,MAAM,SAAS;IAC7B;IACA,MAAM,KAAK,IAAI,EAAM,SAAS,GAAG,EAAM,OAAO,CAAM;GACtD;GAGA,OAFA,KAAU,EAAK,MACf,EAAM,MAAM,KAAK,CAAI,GACd;EACT,GAEM,IAAO,IAAI,gBAAgB,GAC3B,IAAS,EAAE,SAAS,YAAY,IAAI,CAAC,EAAE,QAAQ,EAAK,MAAM,CAAC,IAAI,EAAK,QAGtE,IAAU,GACR,IAA0B,CAAC,GAC3B,UACJ,IAAI,SAAe,GAAS,MAAW;GACrC,IAAM,UAAa;IAGjB,AAFA,EAAO,oBAAoB,SAAS,CAAI,GACxC,KACA,EAAQ;GACV,GACM,UAAa;IAEjB,AADA,EAAQ,OAAO,EAAQ,QAAQ,CAAI,MAAM,GAAG,CAAC,GAC7C,EAAO,EAAQ,CAAM,CAAC;GACxB;GACA,IAAI,IAAU,EAAM,aAAa,OAAO,EAAK;GAC7C,IAAI,EAAO,SAAS,OAAO,EAAO,EAAQ,CAAM,CAAC;GAEjD,AADA,EAAO,iBAAiB,SAAS,GAAM,EAAE,MAAM,GAAK,CAAC,GACrD,EAAQ,KAAK,CAAI;EACnB,CAAC,GACG,UAAgB;GAEpB,KADA,KACO,IAAU,EAAM,eAAe,EAAQ,SAAQ,EAAQ,MAAM,CAAC,CAAE;EACzE,GACM,IAAM,OAAO,MAAsB;GACvC,IAAM,IAAO,EAAK,MAAM,EAAK,QAAQ,EAAK,SAAS,EAAK,IAAI;GAC5D,MAAM,KAAK,MAAM,YAAY;IAC3B,AAAK,EAAK,WACR,EAAK,SAAS,MAAM,EAAU,GAAM,EAAE,UAAO,CAAC,GAC9C,EAAU;IAEZ,IAAM,EAAE,aAAU,MAAM,KAAK,IAAI,MAC/B,EAAM,KACN;KAAE,QAAQ,EAAM;KAAQ,OAAO,CAAC;MAAE,QAAQ,EAAK;MAAQ,MAAM,EAAK;MAAM,QAAQ,EAAK;KAAO,CAAC;IAAE,GAC/F,CACF,GACM,IAAM,EAAM,EAAE,EAAE;IACtB,MAAM,EAAQ;IACd,IAAM,IAAQ,YAAY,IAAI,GACxB,IAAY;IAClB,IAAI;KACF,MAAM,KAAK,UAAU,GAAK,GAAM;MAC9B;MACA,aAAa,MAAM;OAEjB,AADA,EAAQ,IAAI,EAAK,QAAQ,CAAC,GAC1B,EAAa;MACf;KACF,CAAC;IACH,UAAU;KAER,AADA,EAAQ,OAAO,EAAK,MAAM,GAC1B,EAAQ;IACV;IAGA,AAFA,EAAM,OAAO,EAAK,MAAM,YAAY,IAAI,IAAI,GAAO,CAAS,GAC5D,KAAQ,EAAK,MACb,EAAa;GACf,GAAG,CAAM;EACX;EAGA,AADA,EAAU,GACV,EAAa;EACb,IAAM,oBAAW,IAAI,IAAmB,GACpC;EACJ,SAAS;GACP,IAAI;GACJ,OAAO,MAAY,KAAA,KAAa,EAAS,OAAO,EAAM,cAAc,MAAM,IAAO,EAAK,KAAI;IACxF,EAAU;IACV,IAAM,IAAqB,EAAI,CAAI,CAAC,CAAC,WAC7B,KAAK,EAAS,OAAO,CAAG,IAC7B,MAAQ;KAEP,AADA,EAAS,OAAO,CAAG,GACf,MAAY,KAAA,MACd,IAAU,GACV,EAAK,MAAM;IAEf,CACF;IACA,EAAS,IAAI,CAAG;GAClB;GACA,IAAI,EAAS,SAAS,GAAG;GACzB,MAAM,QAAQ,KAAK,CAAQ;EAC7B;EACA,IAAI,MAAY,KAAA,GACd,MAAM,EAAE,QAAQ,UAAU,EAAQ,EAAE,MAAM,IAAI;EAEhD,OAAO,KAAK,SAAS,GAAO,CAAC;CAC/B;CAEA,MAAc,SAAS,GAAoB,GAAyC;EAClF,EAAE,aAAa;GAAE,OAAO;GAAc,QAAQ,EAAM;GAAM,OAAO,EAAM;EAAK,CAAC;EAC7E,IAAM,IAAI,MAAM,KAAK,YAAY,KAAK,IAAI,SAAS,EAAM,KAAK,EAAE,QAAQ,EAAM,OAAO,GAAG,EAAE,MAAM,GAAG,EAAE,MAAM;EAE3G,OADA,EAAE,UAAU,IAAI,GACT;GAAE,MAAM,EAAM;GAAM,MAAM,EAAE;GAAM,MAAM,EAAE;GAAM,MAAM,EAAE;GAAM,QAAQ;GAAO,iBAAiB,EAAM;EAAgB;CAC7H;CAEA,MAAc,MAAS,GAAsB,GAAsB,GAA8D;EAC/H,KAAK,IAAI,IAAU,IAAK,KACtB,IAAI;GACF,OAAO,MAAM,EAAG;EAClB,SAAS,GAAG;GACV,IAAM,IAAM,EAAkB,CAAC;GAC/B,IAAI,GAAQ,SAAS,MAAM,EAAQ,CAAM;GACzC,IAAI,CAAC,EAAI,aAAa,KAAW,KAAK,SAAS,MAAM;GAErD,AADA,MAAM,EAAM,KAAK,MAAM,GAAS,EAAI,UAAU,GAAG,CAAM,GACvD,MAAM,IAAS,CAAG;EACpB;CAEJ;AACF;AAEA,SAAS,EAAY,GAAoC;CACvD,OAAO;EAAE,MAAM,EAAE;EAAM,cAAc,EAAE;EAAc,MAAM,EAAE;CAAK;AACpE;AAGA,SAAgB,EAAQ,GAAiB,GAA6B;CAGpE,OAFI,IAAmB,IAAa,MACvB,KAAK,IAAI,KAAQ,MAAO,KAAK,CACnC,KAAQ,MAAO,KAAK,OAAO,IAAI;AACxC;AAEA,SAAgB,EAAM,GAAY,GAAqC;CACrE,OAAO,IAAI,SAAS,GAAS,MAAW;EACtC,IAAI,GAAQ,SAAS,OAAO,EAAO,EAAQ,CAAM,CAAC;EAClD,IAAM,IAAI,iBAAiB;GAEzB,AADA,GAAQ,oBAAoB,SAAS,CAAO,GAC5C,EAAQ;EACV,GAAG,CAAE,GACC,UAAgB;GAEpB,AADA,aAAa,CAAC,GACd,EAAO,EAAQ,CAAM,CAAC;EACxB;EACA,GAAQ,iBAAiB,SAAS,GAAS,EAAE,MAAM,GAAK,CAAC;CAC3D,CAAC;AACH"}
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../../../src/client/media/client.ts"],"sourcesContent":["import { ContentKitError, aborted, failureError, throwIfAborted, toContentKitError } from \"../errors.js\";\nimport type { CommitBody, Edit, FileInfo, InlineImage, Op, PresetRule, PresignReply, ReadResult, RefBody, RequestReply } from \"../generated/wire.js\";\nimport type { Http } from \"../http.js\";\nimport { call } from \"../route.js\";\nimport { MediaApi, type ReadOptions } from \"./api.js\";\nimport { sha256Hex } from \"./hash.js\";\nimport { Pacer } from \"./pacer.js\";\nimport { itemRef } from \"./ref.js\";\nimport { defaultTransport, type Transport } from \"./transport.js\";\n\n/** Upload tuning for the media module. */\nexport interface MediaOptions {\n /** Sends presigned PUTs. Default XMLHttpRequest (upload progress), else fetch. */\n transport?: Transport;\n /** Parallel parts per multipart upload (the pacer may run fewer). Default 4. */\n concurrency?: number;\n /** Retries per part and per idempotent API call. Default 5. */\n retries?: number;\n /** Delay before retry attempt (0-based); retryAfter is the server's hint in seconds. */\n retryDelay?: (attempt: number, retryAfter?: number) => number;\n /** Seconds one part should take. Default 30. */\n targetPartSeconds?: number;\n}\n\nexport interface Progress {\n /** processing: committed, waiting for the worker (put). */\n phase: \"hashing\" | \"uploading\" | \"completing\" | \"processing\";\n loaded: number;\n total: number;\n}\n\nexport interface UploadOptions {\n ref: RefBody;\n /** The upload path: a literal (\"cover\") or under a pattern (\"originals/001.png\"); the server cleans it and adds the extension. */\n path: string;\n /** Content type; default the file's. */\n type?: string;\n /** Aborting pauses: a multipart upload stays resumable from the last state. */\n signal?: AbortSignal;\n onProgress?: (p: Progress) => void;\n /** State saved from onState, to resume a multipart upload of the same file. */\n resume?: UploadState;\n /** Resumable state after every change; null once the upload completed. Persist it to survive a reload. */\n onState?: (s: UploadState | null) => void;\n}\n\n/** An upload, ready for a put op. */\nexport interface UploadedFile {\n /** The path to commit: cleaned, with an extension, named by the server for a Named upload. */\n path: string;\n /**\n * The name to commit: the folder's identical blob (\"sha256-{hex}-{uuid}\") when\n * exists, else the staged upload (\"u-{uuid}\") the worker hashes and places\n * after the commit.\n */\n blob: string;\n type: string;\n size: number;\n /** The identical file was already in the item's folder; nothing was sent. */\n exists: boolean;\n /** The host processes files on upload: commit it unattached now (the queue does). */\n processOnUpload?: boolean;\n}\n\n/** The put op's fields besides path and blob. */\nexport interface PutOptions {\n /** Refuse to replace an existing upload. Default false. */\n createOnly?: boolean;\n edit?: Edit | null;\n meta?: Record<string, unknown>;\n index?: number;\n /** Wait until the worker processed the upload (waitFor). Default true. */\n wait?: boolean;\n /** How long to wait, ms. */\n timeout?: number;\n /** The upload, before the commit. */\n onUploaded?: (up: UploadedFile) => void;\n}\n\nexport interface WaitOptions {\n signal?: AbortSignal;\n /** Poll interval, ms. Default 1000. */\n interval?: number;\n /** ms; then render_timeout. Default 120000. */\n timeout?: number;\n}\n\nexport interface PlannedPart {\n number: number;\n offset: number;\n size: number;\n sha256?: string;\n}\n\n/** A multipart upload in flight; JSON-serializable. */\nexport interface UploadState {\n ticket: string;\n path: string;\n /** The staged upload (\"u-{uuid}\") the parts write. */\n blob: string;\n type: string;\n size: number;\n ref: RefBody;\n file: { name?: string; lastModified?: number; size: number };\n limits: { minPartSize: number; maxPartSize: number; maxParts: number };\n parts: PlannedPart[];\n processOnUpload?: boolean;\n}\n\n/** An upload to a server-named (Named) path: a post's or poll's image. */\nexport interface NamedOptions {\n /** The requested path; the server names the file. Default the file's name. */\n path?: string;\n signal?: AbortSignal;\n onProgress?: (p: Progress) => void;\n /** Wait until the worker processed it. Default true. */\n wait?: boolean;\n /** How long to wait, ms. */\n timeout?: number;\n /** A crop or rotation the file is committed with. */\n edit?: Edit;\n}\n\n/** An upload to a Named path, committed. */\nexport interface NamedUpload {\n /** The server's name for it (\"i-{uuid}\"), which content routes take. */\n name: string;\n path: string;\n file: FileInfo;\n}\n\n/** The media kind of poll folders (content.Media.PollKind). A content item's folder is its kind's. */\nexport interface ContentFolders {\n /** Default \"poll\". */\n poll?: string;\n}\n\nconst named = /^i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;\n\n/** A file to upload again when its blob is gone at commit; type defaults to the file's. */\nexport type CommitSource = Blob | { file: Blob; type?: string };\n\nexport interface CommitOptions {\n signal?: AbortSignal;\n /** UUID for this complete batch. Keep it with unchanged ops for a later retry; default a new UUID. */\n operationID?: string;\n /** Persist the active batch before sending it, including any replacement after re-upload. Resume with its ops and operation_id. */\n onState?: (batch: CommitBody) => void;\n /** Blob (\"sha256-…\") → the file it was uploaded from. */\n sources?: Record<string, CommitSource>;\n}\n\ntype Uploadable = Blob & { name?: string; lastModified?: number };\n\n/** A path names a file by its full path or, for an upload, its stem (\"cover\" for \"cover.png\"). */\nexport const samePath = (file: string, path: string) => file === path || stem(file) === path;\n\n/** The path without its extension. */\nexport const stem = (path: string) => {\n const i = path.lastIndexOf(\".\");\n return i > path.lastIndexOf(\"/\") + 1 ? path.slice(0, i) : path;\n};\n\n/** The media module: uploads, commits, reads, frames and HLS. */\nexport class MediaClient {\n readonly api: MediaApi;\n private readonly transport: Transport;\n private readonly concurrency: number;\n private readonly retries: number;\n private readonly delay: (attempt: number, retryAfter?: number) => number;\n private readonly targetSeconds: number;\n private presetList?: Promise<PresetRule[]>;\n\n private readonly folders: Required<ContentFolders>;\n\n constructor(\n private readonly http: Http,\n o: MediaOptions = {},\n folders: ContentFolders = {},\n ) {\n this.folders = { poll: folders.poll ?? \"poll\" };\n this.api = new MediaApi(http);\n this.transport = o.transport ?? defaultTransport;\n this.concurrency = o.concurrency ?? 4;\n this.retries = o.retries ?? 5;\n this.delay = o.retryDelay ?? backoff;\n this.targetSeconds = o.targetPartSeconds ?? 30;\n }\n\n /**\n * Uploads file to the item's folder: it hashes the whole file (off the\n * main thread) and presigns {path, type, size, sha256}, then sends nothing\n * (the folder holds the identical blob), or stages it: one checksum-bound\n * PUT up to 64 MiB, resumable multipart above. A refused presign throws\n * before any bytes move. Commit the result with a put op (or use put());\n * the worker then places a staged upload at the hash of its bytes.\n */\n async upload(file: Uploadable, o: UploadOptions): Promise<UploadedFile> {\n const type = o.type || file.type;\n if (!type) throw new ContentKitError(\"invalid_request\", \"the file has no content type\");\n throwIfAborted(o.signal);\n if (o.resume) return this.resumeMultipart(file, o.resume, o);\n const total = file.size;\n const sha256 = await sha256Hex(file, { signal: o.signal, onProgress: (loaded) => o.onProgress?.({ phase: \"hashing\", loaded, total }) });\n const presign = () => this.api.presign({ ref: o.ref, path: o.path, type, size: total, sha256 }, o.signal);\n let p = await presign();\n const out = (p: PresignReply): UploadedFile => ({ path: p.path, blob: p.blob, type, size: total, exists: !!p.exists, processOnUpload: !!p.process_on_upload });\n if (p.exists) return out(p);\n if (p.multipart) {\n const m = p.multipart;\n const state: UploadState = {\n ticket: m.ticket,\n path: p.path,\n blob: p.blob,\n type,\n size: total,\n ref: o.ref,\n file: fingerprint(file),\n limits: { minPartSize: m.min_part_size, maxPartSize: m.max_part_size, maxParts: m.max_parts },\n parts: [],\n processOnUpload: !!p.process_on_upload,\n };\n return this.multipart(file, state, new Set(), o);\n }\n await this.retry(\n async () => {\n if (p.exists) return;\n await this.transport(p.put!, file, { signal: o.signal, onProgress: (loaded) => o.onProgress?.({ phase: \"uploading\", loaded, total }) });\n },\n o.signal,\n async (err) => {\n // An expired URL: presign again (a Named upload gets a new name).\n if (err.code === \"storage\" && err.status === 403) p = await presign();\n },\n );\n return out(p);\n }\n\n /**\n * Uploads file and commits it as a put to its path (with edit, meta and\n * index), then by default waits until the worker processed it; resolves\n * with the upload as an editor reads it.\n */\n async put(file: Uploadable, o: UploadOptions & PutOptions): Promise<FileInfo> {\n const up = await this.upload(file, o);\n o.onUploaded?.(up);\n const op: Op = { op: \"put\", path: up.path, blob: up.blob };\n if (o.createOnly) op.create_id = crypto.randomUUID();\n if (o.edit) op.edit = o.edit;\n if (o.meta) op.meta = o.meta;\n if (o.index !== undefined) op.index = o.index;\n const files = await this.commit(o.ref, [op], { signal: o.signal, sources: { [up.blob]: { file, type: up.type } } });\n if (o.wait === false) return files.find((f) => f.path === up.path) ?? { path: up.path, type: up.type };\n o.onProgress?.({ phase: \"processing\", loaded: up.size, total: up.size });\n return this.waitFor(o.ref, up.path, { signal: o.signal, timeout: o.timeout });\n }\n\n /**\n * Applies ops to the item in one conditional write; returns its uploads as\n * an editor reads them. With sources (blob → the file uploaded as it), a\n * not_uploaded refusal (the blob expired or is due for cleanup) uploads\n * the affected files again and retries the commit once.\n */\n async commit(ref: RefBody, ops: Op[], o: CommitOptions = {}): Promise<FileInfo[]> {\n const emit = this.http.captureChanges();\n // Snapshot the batch once: retries must not pick up edits to the caller's\n // ops while the first request or its response is still in flight.\n const body: CommitBody = structuredClone({ ref, ops, operation_id: o.operationID ?? crypto.randomUUID() });\n o.onState?.(structuredClone(body));\n let files: FileInfo[];\n try {\n files = (await this.retry(() => this.api.commit(body, o.signal), o.signal)).files;\n } catch (e) {\n if (!(e instanceof ContentKitError) || e.code !== \"not_uploaded\") throw e;\n const sources = o.sources ?? {};\n const puts = body.ops.filter((op) => op.op === \"put\" && op.blob && op.blob in sources);\n const stale = [...new Set(puts.map((op) => op.blob!))].filter((b) => !e.blobs || e.blobs.includes(b));\n if (stale.length === 0) throw e;\n const renamed = new Map<string, string>();\n for (const blob of stale) {\n const src = sources[blob]!;\n const file = src instanceof Blob ? src : src.file;\n const type = src instanceof Blob ? undefined : src.type;\n const op = puts.find((p) => p.blob === blob)!;\n renamed.set(blob, (await this.upload(file, { ref: body.ref, path: op.path!, type, signal: o.signal })).blob);\n }\n const retried = body.ops.map((op) => (op.blob && renamed.has(op.blob) ? { ...op, blob: renamed.get(op.blob) } : op));\n // Re-uploading changes the inputs. This is a new batch, not a retry\n // under the old ID; transport retries retain its new identity.\n const replacement = { ref: body.ref, ops: retried, operation_id: crypto.randomUUID() };\n o.onState?.(structuredClone(replacement));\n files = (await this.retry(() => this.api.commit(replacement, o.signal), o.signal)).files;\n }\n emit({ type: \"media.committed\", ref: itemRef(ref), files });\n return files;\n }\n\n /** The read API: the item's files under prefix in manifest order, with signed URLs for what this viewer may have. */\n async read(ref: RefBody, o: ReadOptions & { signal?: AbortSignal } = {}): Promise<ReadResult> {\n this.api.itemURL(ref); // a bad ref fails at once\n return this.retry(() => this.api.read(ref, o, o.signal), o.signal);\n }\n\n /**\n * Polls an editor read until the upload at path (or with that stem) is\n * processed: placed (not staged), nothing pending and its blob present (a\n * frame grabbed).\n * Rejects with its failure, not_found once it is gone, too_large while the\n * item is full (nothing is processed until uploads are removed),\n * render_timeout after the timeout.\n */\n async waitFor(ref: RefBody, path: string, o: WaitOptions = {}): Promise<FileInfo> {\n const emit = this.http.captureChanges();\n const until = Date.now() + (o.timeout ?? 120_000);\n for (;;) {\n const r = await this.read(ref, { editor: true, prefix: stem(path), signal: o.signal });\n const f = r.files.find((x) => x.upload && samePath(x.path, path));\n if (!f) throw new ContentKitError(\"not_found\", `no upload ${path}`, { status: 404 });\n if (f.failed) throw failureError(f.failed);\n if (!f.pending?.length && !f.staged && (f.size ?? 0) > 0) {\n emit({ type: \"media.processed\", ref: itemRef(ref), file: f });\n return f;\n }\n if (r.full) throw new ContentKitError(\"too_large\", `${path} waits: the item is full; remove uploads to process more`, { status: 413 });\n if (Date.now() >= until) throw new ContentKitError(\"render_timeout\", `${path} is still processing`);\n await sleep(o.interval ?? 1000, o.signal);\n }\n }\n\n /**\n * The upload's editor view, for re-cropping: its URL and the upload's\n * oriented size. Waits while it renders (an editor read asks for it);\n * not_found when the path has no upload, render_timeout after the timeout\n * (default 30 s).\n */\n async editorView(ref: RefBody, path: string, o: WaitOptions = {}): Promise<{ url: string; width: number; height: number; edit?: Edit }> {\n const until = Date.now() + (o.timeout ?? 30_000);\n for (;;) {\n const r = await this.read(ref, { editor: true, prefix: stem(path), signal: o.signal });\n const f = r.files.find((x) => x.upload && samePath(x.path, path));\n if (!f) throw new ContentKitError(\"not_found\", `no upload ${path}`, { status: 404 });\n if (f.editor_url && f.w && f.h) return { url: f.editor_url, width: f.w, height: f.h, ...(f.edit ? { edit: f.edit } : {}) };\n if (f.failed) throw failureError(f.failed);\n if (Date.now() >= until) throw new ContentKitError(\"render_timeout\", `the editor view of ${path} is still rendering`);\n await sleep(o.interval ?? 1000, o.signal);\n }\n }\n\n /**\n * Every kind's public presets, fetched once per client: crop bounds,\n * widths and default images. An item's current image is in its read's\n * `public`, never built from a preset. Shared by every caller, so it takes\n * no signal; a failed fetch is retried on the next call.\n */\n presets(): Promise<PresetRule[]> {\n this.presetList ??= this.retry(() => this.api.presets()).catch((e: unknown) => {\n this.presetList = undefined;\n throw e;\n });\n return this.presetList;\n }\n\n /** A JPEG still of the video upload at path, t seconds in, w pixels wide (default the frame's). */\n getFrame(ref: RefBody, path: string, t: number, w?: number, signal?: AbortSignal): Promise<Blob> {\n return this.retry(() => this.api.frame(ref, path, t, w, signal), signal);\n }\n\n /** The folder of an HLS ladder from a read's `hls`: `{media}/{kind}/{id}/hls/{dir}`; master.m3u8 and sprite.vtt resolve under it. */\n hlsBase(ref: RefBody, dir: string): string {\n return `${this.api.itemURL(ref)}/hls/${dir}`;\n }\n\n /** hls.js `xhrSetup` for playlists on the API (the client's token and credentials); pass it to players as is. */\n get xhrSetup(): (xhr: XMLHttpRequest, url: string) => Promise<void> {\n return this.http.xhrSetup;\n }\n\n /**\n * Uploads an image to the item's server-named upload path and commits it\n * (by default waiting until it is processed); resolves with the name content\n * routes take: a poll or option image, a post cover.\n */\n async uploadNamed(ref: RefBody, file: Uploadable, o: NamedOptions = {}): Promise<NamedUpload> {\n const f = await this.put(file, { ref, path: o.path ?? (file.name || \"image\"), signal: o.signal, onProgress: o.onProgress, wait: o.wait, timeout: o.timeout, edit: o.edit });\n const name = stem(f.path.slice(f.path.lastIndexOf(\"/\") + 1));\n if (!named.test(name)) throw new ContentKitError(\"invalid_request\", `${f.path} is not a server-named upload path of ${ref.kind}`);\n return { name, path: f.path, file: f };\n }\n\n /**\n * An image for a content item's rich-text body, uploaded to the item's body\n * folder ({kind}.body and its id, BODY_SUFFIX), which only viewers who open\n * the item see; resolves with its reference to store in the body (`ref`) and\n * a URL that shows it to the editor now (`url`, null while none exists).\n */\n async uploadInline(item: RefBody, file: Uploadable, o: NamedOptions = {}): Promise<NamedUpload & InlineImage> {\n const up = await this.uploadNamed(item, file, o);\n const r: InlineImage = await call(this.http, \"POST\", \"/contents/{id}/images\", { params: { id: item.id }, body: { image: up.name }, signal: o.signal });\n return { ...up, ...r };\n }\n\n /** The media folder of a poll. */\n pollRef(id: string): RefBody {\n return { kind: this.folders.poll, id };\n }\n\n /** Discards a paused multipart upload. */\n async discard(state: UploadState): Promise<void> {\n await this.api.abort(state.ref, { ticket: state.ticket });\n }\n\n private async resumeMultipart(file: Uploadable, state: UploadState, o: UploadOptions): Promise<UploadedFile> {\n const fp = fingerprint(file);\n if (fp.size !== state.size || fp.name !== state.file.name || fp.lastModified !== state.file.lastModified) {\n throw new ContentKitError(\"resume_mismatch\", \"the saved upload is for a different file\");\n }\n let listed;\n try {\n listed = await this.retry(() => this.api.listParts(state.ref, { ticket: state.ticket }, o.signal), o.signal);\n } catch (err) {\n // Gone: already completed (Complete is idempotent) or expired.\n if (err instanceof ContentKitError && err.code === \"not_found\") return this.complete(state, o);\n throw err;\n }\n const landed = new Set<number>();\n for (const l of listed.parts) {\n const plan = state.parts.find((p) => p.number === l.number);\n if (!plan || plan.size !== l.size || (l.sha256 && plan.sha256 !== l.sha256)) {\n // Parts this state does not describe: the upload cannot be finished consistently.\n await this.discard(state).catch(() => {});\n o.onState?.(null);\n return this.upload(file, { ...o, resume: undefined });\n }\n landed.add(l.number);\n }\n return this.multipart(file, structuredClone(state), landed, o);\n }\n\n private async multipart(file: Uploadable, state: UploadState, landed: Set<number>, o: UploadOptions): Promise<UploadedFile> {\n const pacer = new Pacer({ ...state.limits, maxConcurrency: this.concurrency, targetSeconds: this.targetSeconds });\n const emitState = () => o.onState?.(structuredClone(state));\n const sending = new Map<number, number>();\n let sent = state.parts.filter((p) => landed.has(p.number)).reduce((n, p) => n + p.size, 0);\n const emitProgress = () => {\n let loaded = sent;\n for (const n of sending.values()) loaded += n;\n o.onProgress?.({ phase: \"uploading\", loaded, total: state.size });\n };\n const pending = state.parts.filter((p) => !landed.has(p.number));\n let offset = state.parts.reduce((end, p) => Math.max(end, p.offset + p.size), 0);\n const next = (): PlannedPart | undefined => {\n const p = pending.shift();\n if (p || offset >= state.size) return p;\n const part: PlannedPart = {\n number: state.parts.length + 1,\n offset,\n size: Math.min(pacer.partSize(), state.size - offset),\n };\n offset += part.size;\n state.parts.push(part);\n return part;\n };\n\n const stop = new AbortController();\n const signal = o.signal ? AbortSignal.any([o.signal, stop.signal]) : stop.signal;\n // Parts hash and presign ahead of the PUT slots, so a freed slot starts\n // its next PUT at once; pacer.concurrency bounds only the PUTs.\n let putting = 0;\n const waiting: (() => void)[] = [];\n const acquire = () =>\n new Promise<void>((resolve, reject) => {\n const take = () => {\n signal.removeEventListener(\"abort\", drop);\n putting++;\n resolve();\n };\n const drop = () => {\n waiting.splice(waiting.indexOf(take) >>> 0, 1);\n reject(aborted(signal));\n };\n if (putting < pacer.concurrency) return take();\n if (signal.aborted) return reject(aborted(signal));\n signal.addEventListener(\"abort\", drop, { once: true });\n waiting.push(take);\n });\n const release = () => {\n putting--;\n while (putting < pacer.concurrency && waiting.length) waiting.shift()!();\n };\n const run = async (part: PlannedPart) => {\n const body = file.slice(part.offset, part.offset + part.size);\n await this.retry(async () => {\n if (!part.sha256) {\n part.sha256 = await sha256Hex(body, { signal });\n emitState();\n }\n const { parts } = await this.api.parts(\n state.ref,\n { ticket: state.ticket, parts: [{ number: part.number, size: part.size, sha256: part.sha256 }] },\n signal,\n );\n const req = parts[0]?.request as RequestReply;\n await acquire();\n const began = performance.now();\n const alongside = putting;\n try {\n await this.transport(req, body, {\n signal,\n onProgress: (n) => {\n sending.set(part.number, n);\n emitProgress();\n },\n });\n } finally {\n sending.delete(part.number);\n release();\n }\n pacer.record(part.size, performance.now() - began, alongside);\n sent += part.size;\n emitProgress();\n }, signal);\n };\n\n emitState();\n emitProgress();\n const inFlight = new Set<Promise<void>>();\n let failure: unknown;\n for (;;) {\n let part: PlannedPart | undefined;\n while (failure === undefined && inFlight.size < pacer.concurrency + 2 && (part = next())) {\n emitState();\n const job: Promise<void> = run(part).then(\n () => void inFlight.delete(job),\n (err) => {\n inFlight.delete(job);\n if (failure === undefined) {\n failure = err;\n stop.abort();\n }\n },\n );\n inFlight.add(job);\n }\n if (inFlight.size === 0) break;\n await Promise.race(inFlight);\n }\n if (failure !== undefined) {\n throw o.signal?.aborted ? aborted(o.signal) : failure;\n }\n return this.complete(state, o);\n }\n\n private async complete(state: UploadState, o: UploadOptions): Promise<UploadedFile> {\n o.onProgress?.({ phase: \"completing\", loaded: state.size, total: state.size });\n const c = await this.retry(() => this.api.complete(state.ref, { ticket: state.ticket }, o.signal), o.signal);\n o.onState?.(null);\n return { path: state.path, blob: c.blob, type: c.type, size: c.size, exists: false, processOnUpload: state.processOnUpload };\n }\n\n private async retry<T>(fn: () => Promise<T>, signal?: AbortSignal, before?: (err: ContentKitError) => Promise<void>): Promise<T> {\n for (let attempt = 0; ; attempt++) {\n try {\n return await fn();\n } catch (e) {\n const err = toContentKitError(e);\n if (signal?.aborted) throw aborted(signal);\n if (!err.transient || attempt >= this.retries) throw err;\n await sleep(this.delay(attempt, err.retryAfter), signal);\n await before?.(err);\n }\n }\n }\n}\n\nfunction fingerprint(f: Uploadable): UploadState[\"file\"] {\n return { name: f.name, lastModified: f.lastModified, size: f.size };\n}\n\n/** Exponential backoff from 1 s to 30 s with jitter, or the server's Retry-After. */\nexport function backoff(attempt: number, retryAfter?: number): number {\n if (retryAfter) return retryAfter * 1000;\n const base = Math.min(30_000, 1000 * 2 ** attempt);\n return base * (0.75 + Math.random() * 0.5);\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) return reject(aborted(signal));\n const t = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(t);\n reject(aborted(signal));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n"],"mappings":"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"}
|