@anvilkit/plugin-asset-manager 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/dist/adapters/data-url.cjs +78 -0
  4. package/dist/adapters/data-url.d.cts +6 -0
  5. package/dist/adapters/data-url.d.cts.map +1 -0
  6. package/dist/adapters/data-url.d.ts +6 -0
  7. package/dist/adapters/data-url.d.ts.map +1 -0
  8. package/dist/adapters/data-url.js +44 -0
  9. package/dist/adapters/extract-image-dimensions.cjs +69 -0
  10. package/dist/adapters/extract-image-dimensions.d.cts +21 -0
  11. package/dist/adapters/extract-image-dimensions.d.cts.map +1 -0
  12. package/dist/adapters/extract-image-dimensions.d.ts +21 -0
  13. package/dist/adapters/extract-image-dimensions.d.ts.map +1 -0
  14. package/dist/adapters/extract-image-dimensions.js +35 -0
  15. package/dist/adapters/in-memory.cjs +62 -0
  16. package/dist/adapters/in-memory.d.cts +3 -0
  17. package/dist/adapters/in-memory.d.cts.map +1 -0
  18. package/dist/adapters/in-memory.d.ts +3 -0
  19. package/dist/adapters/in-memory.d.ts.map +1 -0
  20. package/dist/adapters/in-memory.js +28 -0
  21. package/dist/adapters/s3-presigned.cjs +166 -0
  22. package/dist/adapters/s3-presigned.d.cts +59 -0
  23. package/dist/adapters/s3-presigned.d.cts.map +1 -0
  24. package/dist/adapters/s3-presigned.d.ts +59 -0
  25. package/dist/adapters/s3-presigned.d.ts.map +1 -0
  26. package/dist/adapters/s3-presigned.js +129 -0
  27. package/dist/asset-reference.cjs +38 -0
  28. package/dist/asset-reference.d.cts +10 -0
  29. package/dist/asset-reference.d.cts.map +1 -0
  30. package/dist/asset-reference.d.ts +10 -0
  31. package/dist/asset-reference.d.ts.map +1 -0
  32. package/dist/asset-reference.js +4 -0
  33. package/dist/csp.cjs +83 -0
  34. package/dist/csp.d.cts +45 -0
  35. package/dist/csp.d.cts.map +1 -0
  36. package/dist/csp.d.ts +45 -0
  37. package/dist/csp.d.ts.map +1 -0
  38. package/dist/csp.js +49 -0
  39. package/dist/errors.cjs +58 -0
  40. package/dist/errors.d.cts +15 -0
  41. package/dist/errors.d.cts.map +1 -0
  42. package/dist/errors.d.ts +15 -0
  43. package/dist/errors.d.ts.map +1 -0
  44. package/dist/errors.js +21 -0
  45. package/dist/header-action.cjs +44 -0
  46. package/dist/header-action.d.cts +3 -0
  47. package/dist/header-action.d.cts.map +1 -0
  48. package/dist/header-action.d.ts +3 -0
  49. package/dist/header-action.d.ts.map +1 -0
  50. package/dist/header-action.js +10 -0
  51. package/dist/index.cjs +90 -0
  52. package/dist/index.d.cts +18 -0
  53. package/dist/index.d.cts.map +1 -0
  54. package/dist/index.d.ts +18 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +10 -0
  57. package/dist/infer-kind.cjs +45 -0
  58. package/dist/infer-kind.d.cts +8 -0
  59. package/dist/infer-kind.d.cts.map +1 -0
  60. package/dist/infer-kind.d.ts +8 -0
  61. package/dist/infer-kind.d.ts.map +1 -0
  62. package/dist/infer-kind.js +11 -0
  63. package/dist/plugin.cjs +258 -0
  64. package/dist/plugin.d.cts +9 -0
  65. package/dist/plugin.d.cts.map +1 -0
  66. package/dist/plugin.d.ts +9 -0
  67. package/dist/plugin.d.ts.map +1 -0
  68. package/dist/plugin.js +212 -0
  69. package/dist/registry.cjs +198 -0
  70. package/dist/registry.d.cts +3 -0
  71. package/dist/registry.d.cts.map +1 -0
  72. package/dist/registry.d.ts +3 -0
  73. package/dist/registry.d.ts.map +1 -0
  74. package/dist/registry.js +164 -0
  75. package/dist/resolver.cjs +185 -0
  76. package/dist/resolver.d.cts +10 -0
  77. package/dist/resolver.d.cts.map +1 -0
  78. package/dist/resolver.d.ts +10 -0
  79. package/dist/resolver.d.ts.map +1 -0
  80. package/dist/resolver.js +148 -0
  81. package/dist/retry.cjs +123 -0
  82. package/dist/retry.d.cts +71 -0
  83. package/dist/retry.d.cts.map +1 -0
  84. package/dist/retry.d.ts +71 -0
  85. package/dist/retry.d.ts.map +1 -0
  86. package/dist/retry.js +86 -0
  87. package/dist/studio-asset-source.cjs +211 -0
  88. package/dist/studio-asset-source.d.cts +52 -0
  89. package/dist/studio-asset-source.d.cts.map +1 -0
  90. package/dist/studio-asset-source.d.ts +52 -0
  91. package/dist/studio-asset-source.d.ts.map +1 -0
  92. package/dist/studio-asset-source.js +171 -0
  93. package/dist/testing/index.cjs +66 -0
  94. package/dist/testing/index.d.cts +24 -0
  95. package/dist/testing/index.d.cts.map +1 -0
  96. package/dist/testing/index.d.ts +24 -0
  97. package/dist/testing/index.d.ts.map +1 -0
  98. package/dist/testing/index.js +29 -0
  99. package/dist/types.cjs +18 -0
  100. package/dist/types.d.cts +132 -0
  101. package/dist/types.d.cts.map +1 -0
  102. package/dist/types.d.ts +132 -0
  103. package/dist/types.d.ts.map +1 -0
  104. package/dist/types.js +0 -0
  105. package/dist/ui/AssetBrowser.cjs +271 -0
  106. package/dist/ui/AssetBrowser.d.cts +45 -0
  107. package/dist/ui/AssetBrowser.d.cts.map +1 -0
  108. package/dist/ui/AssetBrowser.d.ts +45 -0
  109. package/dist/ui/AssetBrowser.d.ts.map +1 -0
  110. package/dist/ui/AssetBrowser.js +237 -0
  111. package/dist/ui/AssetCommandPalette.cjs +135 -0
  112. package/dist/ui/AssetCommandPalette.d.cts +21 -0
  113. package/dist/ui/AssetCommandPalette.d.cts.map +1 -0
  114. package/dist/ui/AssetCommandPalette.d.ts +21 -0
  115. package/dist/ui/AssetCommandPalette.d.ts.map +1 -0
  116. package/dist/ui/AssetCommandPalette.js +101 -0
  117. package/dist/ui/AssetManagerUI.cjs +169 -0
  118. package/dist/ui/AssetManagerUI.d.cts +15 -0
  119. package/dist/ui/AssetManagerUI.d.cts.map +1 -0
  120. package/dist/ui/AssetManagerUI.d.ts +15 -0
  121. package/dist/ui/AssetManagerUI.d.ts.map +1 -0
  122. package/dist/ui/AssetManagerUI.js +135 -0
  123. package/dist/ui/DeleteAssetDialog.cjs +70 -0
  124. package/dist/ui/DeleteAssetDialog.d.cts +22 -0
  125. package/dist/ui/DeleteAssetDialog.d.cts.map +1 -0
  126. package/dist/ui/DeleteAssetDialog.d.ts +22 -0
  127. package/dist/ui/DeleteAssetDialog.d.ts.map +1 -0
  128. package/dist/ui/DeleteAssetDialog.js +36 -0
  129. package/dist/ui/MetadataPanel.cjs +147 -0
  130. package/dist/ui/MetadataPanel.d.cts +21 -0
  131. package/dist/ui/MetadataPanel.d.cts.map +1 -0
  132. package/dist/ui/MetadataPanel.d.ts +21 -0
  133. package/dist/ui/MetadataPanel.d.ts.map +1 -0
  134. package/dist/ui/MetadataPanel.js +113 -0
  135. package/dist/ui/ReplaceAssetDialog.cjs +125 -0
  136. package/dist/ui/ReplaceAssetDialog.d.cts +14 -0
  137. package/dist/ui/ReplaceAssetDialog.d.cts.map +1 -0
  138. package/dist/ui/ReplaceAssetDialog.d.ts +14 -0
  139. package/dist/ui/ReplaceAssetDialog.d.ts.map +1 -0
  140. package/dist/ui/ReplaceAssetDialog.js +91 -0
  141. package/dist/ui/UploadButton.cjs +189 -0
  142. package/dist/ui/UploadButton.d.cts +17 -0
  143. package/dist/ui/UploadButton.d.cts.map +1 -0
  144. package/dist/ui/UploadButton.d.ts +17 -0
  145. package/dist/ui/UploadButton.d.ts.map +1 -0
  146. package/dist/ui/UploadButton.js +155 -0
  147. package/dist/ui/index.cjs +60 -0
  148. package/dist/ui/index.d.cts +15 -0
  149. package/dist/ui/index.d.cts.map +1 -0
  150. package/dist/ui/index.d.ts +15 -0
  151. package/dist/ui/index.d.ts.map +1 -0
  152. package/dist/ui/index.js +7 -0
  153. package/dist/validate-upload-result.cjs +149 -0
  154. package/dist/validate-upload-result.d.cts +9 -0
  155. package/dist/validate-upload-result.d.cts.map +1 -0
  156. package/dist/validate-upload-result.d.ts +9 -0
  157. package/dist/validate-upload-result.d.ts.map +1 -0
  158. package/dist/validate-upload-result.js +115 -0
  159. package/package.json +131 -0
@@ -0,0 +1,148 @@
1
+ import { AssetResolutionError, AssetValidationError } from "./errors.js";
2
+ import { validateUploadResult } from "./validate-upload-result.js";
3
+ const URL_REJECTION_VALIDATION_CODES = new Set([
4
+ "EMPTY_UPLOAD_URL",
5
+ "UNSCHEMED_UPLOAD_URL",
6
+ "DISALLOWED_UPLOAD_URL_SCHEME",
7
+ "INVALID_UPLOAD_ID",
8
+ "PATH_TRAVERSAL_URL",
9
+ "MIXED_SCRIPT_HOSTNAME"
10
+ ]);
11
+ const ASSET_REFERENCE_PREFIX = "asset://";
12
+ const ASSET_PROP_KEYS = new Set([
13
+ "src",
14
+ "imageUrl",
15
+ "imageSrc",
16
+ "url",
17
+ "videoUrl",
18
+ "videoSrc",
19
+ "fontUrl",
20
+ "scriptUrl",
21
+ "styleUrl",
22
+ "backgroundSrc",
23
+ "backgroundImage",
24
+ "poster",
25
+ "thumbnailSrc"
26
+ ]);
27
+ function createIRAssetResolver(options) {
28
+ return (url)=>{
29
+ const assetId = parseAssetReference(url);
30
+ if (null === assetId) return null;
31
+ const asset = options.registry.get(assetId);
32
+ if (!asset) throw new AssetResolutionError(assetId, "ASSET_NOT_FOUND");
33
+ try {
34
+ const validated = validateUploadResult({
35
+ id: asset.id,
36
+ url: asset.url,
37
+ ...asset.meta ? {
38
+ meta: asset.meta
39
+ } : {}
40
+ }, {
41
+ dataUrlAllowlistOptIn: options.dataUrlAllowlistOptIn,
42
+ allowMixedScriptHostnames: options.allowMixedScriptHostnames
43
+ });
44
+ const resolution = {
45
+ url: validated.url,
46
+ ...validated.meta ? {
47
+ meta: validated.meta
48
+ } : {}
49
+ };
50
+ return resolution;
51
+ } catch (error) {
52
+ const message = error instanceof Error ? error.message : `Could not resolve asset "${assetId}"`;
53
+ const code = error instanceof AssetValidationError && URL_REJECTION_VALIDATION_CODES.has(error.code) ? "ASSET_URL_REJECTED" : "ASSET_VALIDATION_FAILED";
54
+ throw new AssetResolutionError(assetId, code, message, {
55
+ cause: error
56
+ });
57
+ }
58
+ };
59
+ }
60
+ async function resolveAssets(ir, resolver) {
61
+ const rewriteMap = new Map();
62
+ const assetUrls = collectAssetUrls(ir);
63
+ for (const url of assetUrls){
64
+ const resolution = await resolver(url);
65
+ if (null !== resolution) rewriteMap.set(url, resolution);
66
+ }
67
+ const nextIr = {
68
+ version: ir.version,
69
+ root: cloneNode(ir.root, rewriteMap),
70
+ assets: ir.assets.map((asset)=>cloneAsset(asset, rewriteMap)),
71
+ metadata: {
72
+ ...ir.metadata
73
+ }
74
+ };
75
+ return deepFreeze(nextIr);
76
+ }
77
+ function collectAssetUrls(ir) {
78
+ const urls = new Set();
79
+ for (const asset of ir.assets)if ("" !== asset.url.trim()) urls.add(asset.url);
80
+ collectNodeAssetUrls(ir.root, urls);
81
+ return urls;
82
+ }
83
+ function collectNodeAssetUrls(node, urls) {
84
+ collectValueAssetUrls(node.props, urls);
85
+ if (node.assets) {
86
+ for (const asset of node.assets)if ("" !== asset.url.trim()) urls.add(asset.url);
87
+ }
88
+ if (node.children) for (const child of node.children)collectNodeAssetUrls(child, urls);
89
+ }
90
+ function collectValueAssetUrls(value, urls, key) {
91
+ if (Array.isArray(value)) {
92
+ for (const item of value)collectValueAssetUrls(item, urls);
93
+ return;
94
+ }
95
+ if ("string" == typeof value) {
96
+ if (void 0 !== key && ASSET_PROP_KEYS.has(key) && "" !== value.trim()) urls.add(value);
97
+ return;
98
+ }
99
+ if (null === value || "object" != typeof value) return;
100
+ for (const [entryKey, entryValue] of Object.entries(value))collectValueAssetUrls(entryValue, urls, entryKey);
101
+ }
102
+ function parseAssetReference(url) {
103
+ if (!url.startsWith(ASSET_REFERENCE_PREFIX)) return null;
104
+ const assetId = url.slice(ASSET_REFERENCE_PREFIX.length).trim();
105
+ return "" === assetId ? null : assetId;
106
+ }
107
+ function cloneNode(node, rewriteMap) {
108
+ return {
109
+ id: node.id,
110
+ type: node.type,
111
+ props: cloneValue(node.props, rewriteMap),
112
+ ...node.children ? {
113
+ children: node.children.map((child)=>cloneNode(child, rewriteMap))
114
+ } : {},
115
+ ...node.assets ? {
116
+ assets: node.assets.map((asset)=>cloneAsset(asset, rewriteMap))
117
+ } : {}
118
+ };
119
+ }
120
+ function cloneAsset(asset, rewriteMap) {
121
+ const resolution = rewriteMap.get(asset.url);
122
+ return {
123
+ id: asset.id,
124
+ kind: asset.kind,
125
+ url: resolution?.url ?? asset.url,
126
+ ...resolution?.meta ?? asset.meta ? {
127
+ meta: resolution?.meta ?? asset.meta
128
+ } : {}
129
+ };
130
+ }
131
+ function cloneValue(value, rewriteMap, key) {
132
+ if (Array.isArray(value)) return value.map((item)=>cloneValue(item, rewriteMap));
133
+ if ("string" == typeof value) {
134
+ if (void 0 !== key && !ASSET_PROP_KEYS.has(key)) return value;
135
+ return rewriteMap.get(value)?.url ?? value;
136
+ }
137
+ if (null === value || "object" != typeof value) return value;
138
+ const nextValue = {};
139
+ for (const [entryKey, entryValue] of Object.entries(value))nextValue[entryKey] = cloneValue(entryValue, rewriteMap, entryKey);
140
+ return nextValue;
141
+ }
142
+ function deepFreeze(value) {
143
+ if (null === value || "object" != typeof value || Object.isFrozen(value)) return value;
144
+ Object.freeze(value);
145
+ for (const entry of Object.values(value))deepFreeze(entry);
146
+ return value;
147
+ }
148
+ export { createIRAssetResolver, resolveAssets };
package/dist/retry.cjs ADDED
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ RetryableError: ()=>RetryableError,
28
+ withRetry: ()=>withRetry
29
+ });
30
+ class RetryableError extends Error {
31
+ retryAfterMs;
32
+ constructor(message, options){
33
+ super(message);
34
+ this.name = "RetryableError";
35
+ if (options && "cause" in options) this.cause = options.cause;
36
+ if (options?.retryAfterMs !== void 0) this.retryAfterMs = options.retryAfterMs;
37
+ }
38
+ }
39
+ const DEFAULT_MAX_RETRIES = 3;
40
+ const DEFAULT_BASE_DELAY_MS = 250;
41
+ const DEFAULT_MAX_DELAY_MS = 8000;
42
+ async function withRetry(fn, options = {}) {
43
+ const { maxRetries = DEFAULT_MAX_RETRIES, baseDelayMs = DEFAULT_BASE_DELAY_MS, maxDelayMs = DEFAULT_MAX_DELAY_MS, signal, jitter = Math.random, sleep = defaultSleep } = options;
44
+ let attempt = 0;
45
+ while(true){
46
+ throwIfAborted(signal);
47
+ try {
48
+ return await fn(attempt);
49
+ } catch (error) {
50
+ throwIfAborted(signal);
51
+ if (!isRetryable(error)) throw error;
52
+ if (attempt >= maxRetries) throw error;
53
+ const delay = computeDelay({
54
+ attempt,
55
+ baseDelayMs,
56
+ maxDelayMs,
57
+ jitter,
58
+ retryAfterMs: getRetryAfterMs(error)
59
+ });
60
+ await sleep(delay, signal);
61
+ attempt += 1;
62
+ }
63
+ }
64
+ }
65
+ function isRetryable(error) {
66
+ if (error instanceof RetryableError) return true;
67
+ return null !== error && "object" == typeof error && "RetryableError" === error.name;
68
+ }
69
+ function getRetryAfterMs(error) {
70
+ if (null !== error && "object" == typeof error && "number" == typeof error.retryAfterMs) return error.retryAfterMs;
71
+ }
72
+ function computeDelay(input) {
73
+ if (void 0 !== input.retryAfterMs) return Math.max(0, input.retryAfterMs);
74
+ const exp = Math.min(input.maxDelayMs, input.baseDelayMs * 2 ** input.attempt);
75
+ const half = exp / 2;
76
+ return Math.floor(half + input.jitter() * half);
77
+ }
78
+ function throwIfAborted(signal) {
79
+ if (signal?.aborted) throw makeAbortError(signal);
80
+ }
81
+ function makeAbortError(signal) {
82
+ const reason = signal.reason;
83
+ if (reason instanceof Error) return reason;
84
+ if ("u" > typeof DOMException) return new DOMException("Aborted", "AbortError");
85
+ const error = new Error("Aborted");
86
+ error.name = "AbortError";
87
+ return error;
88
+ }
89
+ function defaultSleep(ms, signal) {
90
+ if (ms <= 0) {
91
+ throwIfAborted(signal);
92
+ return Promise.resolve();
93
+ }
94
+ return new Promise((resolve, reject)=>{
95
+ const timer = setTimeout(()=>{
96
+ signal?.removeEventListener("abort", onAbort);
97
+ resolve();
98
+ }, ms);
99
+ const onAbort = ()=>{
100
+ clearTimeout(timer);
101
+ reject(makeAbortError(signal));
102
+ };
103
+ if (signal) {
104
+ if (signal.aborted) {
105
+ clearTimeout(timer);
106
+ reject(makeAbortError(signal));
107
+ return;
108
+ }
109
+ signal.addEventListener("abort", onAbort, {
110
+ once: true
111
+ });
112
+ }
113
+ });
114
+ }
115
+ exports.RetryableError = __webpack_exports__.RetryableError;
116
+ exports.withRetry = __webpack_exports__.withRetry;
117
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
118
+ "RetryableError",
119
+ "withRetry"
120
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
121
+ Object.defineProperty(exports, '__esModule', {
122
+ value: true
123
+ });
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @file Retry helpers used by network-bound upload adapters.
3
+ *
4
+ * `RetryableError` marks a thrown error as transient. `withRetry()`
5
+ * wraps an async function with exponential backoff plus full-jitter,
6
+ * abort-aware sleep, and an optional `retryAfterMs` override carried
7
+ * on the error.
8
+ *
9
+ * @experimental Public surface may change before v1.0. Pinned by the
10
+ * package's `api/api-snapshot.json` once the snapshot script is
11
+ * extended to scan this entrypoint.
12
+ */
13
+ /**
14
+ * Marks an error as transient — `withRetry()` will reschedule the
15
+ * underlying call rather than rethrow.
16
+ *
17
+ * Adapters should throw `RetryableError` for HTTP 5xx, network
18
+ * failures, and other recoverable conditions; non-retryable errors
19
+ * (4xx, schema mismatches, abort) should be thrown as plain `Error`
20
+ * subclasses (typically `AssetValidationError`).
21
+ *
22
+ * The optional `retryAfterMs` overrides the next computed delay,
23
+ * useful when the server returned a `Retry-After` header.
24
+ */
25
+ export declare class RetryableError extends Error {
26
+ readonly retryAfterMs?: number;
27
+ constructor(message: string, options?: {
28
+ readonly cause?: unknown;
29
+ readonly retryAfterMs?: number;
30
+ });
31
+ }
32
+ export interface RetryOptions {
33
+ /**
34
+ * Maximum number of retry attempts after the initial call.
35
+ * `maxRetries: 3` means up to 4 total invocations.
36
+ *
37
+ * @default 3
38
+ */
39
+ readonly maxRetries?: number;
40
+ /**
41
+ * Base delay in milliseconds. The actual delay grows exponentially
42
+ * (`baseDelayMs * 2^attempt`) and is then jittered.
43
+ *
44
+ * @default 250
45
+ */
46
+ readonly baseDelayMs?: number;
47
+ /**
48
+ * Cap on the computed backoff delay (before jitter).
49
+ *
50
+ * @default 8000
51
+ */
52
+ readonly maxDelayMs?: number;
53
+ /** Aborts both the in-flight call and any pending retry sleep. */
54
+ readonly signal?: AbortSignal;
55
+ /** Defaults to `Math.random`. Override for deterministic tests. */
56
+ readonly jitter?: () => number;
57
+ /** Defaults to a `setTimeout`-based, abort-aware sleep. */
58
+ readonly sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
59
+ }
60
+ /**
61
+ * Run `fn` and retry on `RetryableError` with full-jitter exponential
62
+ * backoff. Honors `signal` between attempts and during sleep.
63
+ *
64
+ * Resolution order on each error:
65
+ * 1. If the signal is aborted → throw `AbortError` immediately.
66
+ * 2. If the error is not a `RetryableError` → rethrow.
67
+ * 3. If retries are exhausted → rethrow the last error.
68
+ * 4. Otherwise compute backoff, sleep, and try again.
69
+ */
70
+ export declare function withRetry<T>(fn: (attempt: number) => Promise<T>, options?: RetryOptions): Promise<T>;
71
+ //# sourceMappingURL=retry.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.d.cts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;;;;;;;;GAWG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACxC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;gBAG9B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE;CAYvE;AAED,MAAM,WAAW,YAAY;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,mEAAmE;IACnE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;IAC/B,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrE;AAMD;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAChC,EAAE,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EACnC,OAAO,GAAE,YAAiB,GACxB,OAAO,CAAC,CAAC,CAAC,CAmCZ"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @file Retry helpers used by network-bound upload adapters.
3
+ *
4
+ * `RetryableError` marks a thrown error as transient. `withRetry()`
5
+ * wraps an async function with exponential backoff plus full-jitter,
6
+ * abort-aware sleep, and an optional `retryAfterMs` override carried
7
+ * on the error.
8
+ *
9
+ * @experimental Public surface may change before v1.0. Pinned by the
10
+ * package's `api/api-snapshot.json` once the snapshot script is
11
+ * extended to scan this entrypoint.
12
+ */
13
+ /**
14
+ * Marks an error as transient — `withRetry()` will reschedule the
15
+ * underlying call rather than rethrow.
16
+ *
17
+ * Adapters should throw `RetryableError` for HTTP 5xx, network
18
+ * failures, and other recoverable conditions; non-retryable errors
19
+ * (4xx, schema mismatches, abort) should be thrown as plain `Error`
20
+ * subclasses (typically `AssetValidationError`).
21
+ *
22
+ * The optional `retryAfterMs` overrides the next computed delay,
23
+ * useful when the server returned a `Retry-After` header.
24
+ */
25
+ export declare class RetryableError extends Error {
26
+ readonly retryAfterMs?: number;
27
+ constructor(message: string, options?: {
28
+ readonly cause?: unknown;
29
+ readonly retryAfterMs?: number;
30
+ });
31
+ }
32
+ export interface RetryOptions {
33
+ /**
34
+ * Maximum number of retry attempts after the initial call.
35
+ * `maxRetries: 3` means up to 4 total invocations.
36
+ *
37
+ * @default 3
38
+ */
39
+ readonly maxRetries?: number;
40
+ /**
41
+ * Base delay in milliseconds. The actual delay grows exponentially
42
+ * (`baseDelayMs * 2^attempt`) and is then jittered.
43
+ *
44
+ * @default 250
45
+ */
46
+ readonly baseDelayMs?: number;
47
+ /**
48
+ * Cap on the computed backoff delay (before jitter).
49
+ *
50
+ * @default 8000
51
+ */
52
+ readonly maxDelayMs?: number;
53
+ /** Aborts both the in-flight call and any pending retry sleep. */
54
+ readonly signal?: AbortSignal;
55
+ /** Defaults to `Math.random`. Override for deterministic tests. */
56
+ readonly jitter?: () => number;
57
+ /** Defaults to a `setTimeout`-based, abort-aware sleep. */
58
+ readonly sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
59
+ }
60
+ /**
61
+ * Run `fn` and retry on `RetryableError` with full-jitter exponential
62
+ * backoff. Honors `signal` between attempts and during sleep.
63
+ *
64
+ * Resolution order on each error:
65
+ * 1. If the signal is aborted → throw `AbortError` immediately.
66
+ * 2. If the error is not a `RetryableError` → rethrow.
67
+ * 3. If retries are exhausted → rethrow the last error.
68
+ * 4. Otherwise compute backoff, sleep, and try again.
69
+ */
70
+ export declare function withRetry<T>(fn: (attempt: number) => Promise<T>, options?: RetryOptions): Promise<T>;
71
+ //# sourceMappingURL=retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;;;;;;;;GAWG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACxC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;gBAG9B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE;CAYvE;AAED,MAAM,WAAW,YAAY;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,mEAAmE;IACnE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;IAC/B,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrE;AAMD;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAChC,EAAE,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EACnC,OAAO,GAAE,YAAiB,GACxB,OAAO,CAAC,CAAC,CAAC,CAmCZ"}
package/dist/retry.js ADDED
@@ -0,0 +1,86 @@
1
+ class RetryableError extends Error {
2
+ retryAfterMs;
3
+ constructor(message, options){
4
+ super(message);
5
+ this.name = "RetryableError";
6
+ if (options && "cause" in options) this.cause = options.cause;
7
+ if (options?.retryAfterMs !== void 0) this.retryAfterMs = options.retryAfterMs;
8
+ }
9
+ }
10
+ const DEFAULT_MAX_RETRIES = 3;
11
+ const DEFAULT_BASE_DELAY_MS = 250;
12
+ const DEFAULT_MAX_DELAY_MS = 8000;
13
+ async function withRetry(fn, options = {}) {
14
+ const { maxRetries = DEFAULT_MAX_RETRIES, baseDelayMs = DEFAULT_BASE_DELAY_MS, maxDelayMs = DEFAULT_MAX_DELAY_MS, signal, jitter = Math.random, sleep = defaultSleep } = options;
15
+ let attempt = 0;
16
+ while(true){
17
+ throwIfAborted(signal);
18
+ try {
19
+ return await fn(attempt);
20
+ } catch (error) {
21
+ throwIfAborted(signal);
22
+ if (!isRetryable(error)) throw error;
23
+ if (attempt >= maxRetries) throw error;
24
+ const delay = computeDelay({
25
+ attempt,
26
+ baseDelayMs,
27
+ maxDelayMs,
28
+ jitter,
29
+ retryAfterMs: getRetryAfterMs(error)
30
+ });
31
+ await sleep(delay, signal);
32
+ attempt += 1;
33
+ }
34
+ }
35
+ }
36
+ function isRetryable(error) {
37
+ if (error instanceof RetryableError) return true;
38
+ return null !== error && "object" == typeof error && "RetryableError" === error.name;
39
+ }
40
+ function getRetryAfterMs(error) {
41
+ if (null !== error && "object" == typeof error && "number" == typeof error.retryAfterMs) return error.retryAfterMs;
42
+ }
43
+ function computeDelay(input) {
44
+ if (void 0 !== input.retryAfterMs) return Math.max(0, input.retryAfterMs);
45
+ const exp = Math.min(input.maxDelayMs, input.baseDelayMs * 2 ** input.attempt);
46
+ const half = exp / 2;
47
+ return Math.floor(half + input.jitter() * half);
48
+ }
49
+ function throwIfAborted(signal) {
50
+ if (signal?.aborted) throw makeAbortError(signal);
51
+ }
52
+ function makeAbortError(signal) {
53
+ const reason = signal.reason;
54
+ if (reason instanceof Error) return reason;
55
+ if ("u" > typeof DOMException) return new DOMException("Aborted", "AbortError");
56
+ const error = new Error("Aborted");
57
+ error.name = "AbortError";
58
+ return error;
59
+ }
60
+ function defaultSleep(ms, signal) {
61
+ if (ms <= 0) {
62
+ throwIfAborted(signal);
63
+ return Promise.resolve();
64
+ }
65
+ return new Promise((resolve, reject)=>{
66
+ const timer = setTimeout(()=>{
67
+ signal?.removeEventListener("abort", onAbort);
68
+ resolve();
69
+ }, ms);
70
+ const onAbort = ()=>{
71
+ clearTimeout(timer);
72
+ reject(makeAbortError(signal));
73
+ };
74
+ if (signal) {
75
+ if (signal.aborted) {
76
+ clearTimeout(timer);
77
+ reject(makeAbortError(signal));
78
+ return;
79
+ }
80
+ signal.addEventListener("abort", onAbort, {
81
+ once: true
82
+ });
83
+ }
84
+ });
85
+ }
86
+ export { RetryableError, withRetry };