@hue-run/sdk 0.18.2 → 0.19.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.
package/dist/blobs.d.ts CHANGED
@@ -3,9 +3,11 @@ import { type MediaDigestMemo, type FileDigest } from "./inline-files.js";
3
3
  import { type ByteSource } from "./large-media.js";
4
4
  /**
5
5
  * Large values and binary media travel apart from their span through reservation, presigned PUT,
6
- * and completion. Text keeps a 16 KiB prefix. Binary media keeps digest metadata on its part.
7
- * Successful uploads are listed in `hue.blobs`; failures keep inline fallback or file metadata,
8
- * are reported as warnings, and are counted.
6
+ * and completion. Export hashes each value, writes its reference (text keeps a 16 KiB prefix,
7
+ * binary media digest metadata on its part) and its `hue.blobs` entry, and sends the record; the
8
+ * upload runs afterwards in the background, as Langfuse uploads media. A value refused when its
9
+ * record is exported keeps its inline fallback or file metadata; an upload that fails after export
10
+ * leaves its entry, which Hue reads as missing. Both are reported as warnings and counted.
9
11
  */
10
12
  /** The span attribute listing the values the SDK uploaded apart from the span: one compact JSON
11
13
  * object per value, `{"key", "sha256", "size", "content_type"}`, with an optional `name`. */
@@ -17,18 +19,28 @@ export declare const BLOB_PREFIX_BYTES: number;
17
19
  /** The entries one record's `hue.blobs` may list; Hue reads no more. */
18
20
  export declare const MAX_BLOB_ENTRIES = 64;
19
21
  /**
20
- * The values over the inline limit that queued spans may hold whole for upload at once, charged
21
- * apart from `maxQueueBytes` (a string as two bytes a code unit, bytes as themselves). A value
22
- * the budget cannot hold is cut when it is queued, as before uploads existed; a single value
23
- * larger than the whole budget is held while nothing else is.
22
+ * The values over the inline limit that queued records and unsettled uploads may hold whole at
23
+ * once, charged apart from `maxQueueBytes` (a string as two bytes a code unit, bytes as
24
+ * themselves): each is held until its record is exported and its upload settles. A value the
25
+ * budget cannot hold is cut when it is queued, as before uploads existed; a single value larger
26
+ * than the whole budget is held while nothing else is.
24
27
  */
25
28
  export declare const MAX_HELD_BLOB_BYTES: number;
26
- /** How long one export may spend uploading its values, on top of its requests. */
29
+ /** The longest wait before an upload's next attempt, a `Retry-After` included. */
30
+ export declare const MAX_RETRY_AFTER_MILLIS = 60000;
31
+ /** How long one export may spend hashing its values: past it a value keeps its fallback. Also
32
+ * the import's least upload deadline, and part of an upload's lifetime. */
27
33
  export declare function uploadBudgetMillis(timeoutMillis: number): number;
34
+ /** How long one upload may take from when its record's export queued it: six export timeouts for
35
+ * its requests and up to a minute of Retry-After of its own. */
36
+ export declare function uploadLifetimeMillis(timeoutMillis: number): number;
28
37
  /** Why content kept its fallback instead of being uploaded. */
29
38
  export type FallbackReason = "unsupported" | "failed" | "tooLarge" | "budget";
30
39
  /** The fixed description of each fallback, as an export issue reports it. */
31
40
  export declare const FALLBACK_MESSAGES: Record<FallbackReason, string>;
41
+ /** The fixed description of each failure of an upload that ran after its record was exported:
42
+ * the record lists the value under `hue.blobs`, and Hue reads it as missing. */
43
+ export declare const LATE_FALLBACK_MESSAGES: Record<FallbackReason, string>;
32
44
  /** A span's own attribute value the redaction left whole for the upload step to place. */
33
45
  export interface OffloadCandidate {
34
46
  key: string;
@@ -84,6 +96,21 @@ export interface OffloadTally {
84
96
  }
85
97
  export declare function newTally(): OffloadTally;
86
98
  export declare function countFallback(tally: OffloadTally, reason: FallbackReason, traceId: string): void;
99
+ /** Adds one tally's outcomes to another's. */
100
+ export declare function mergeTally(into: OffloadTally, from: OffloadTally): void;
101
+ /** Adds `from` to `into` once `settled` all settle. Built apart from the step that queued the
102
+ * uploads, so a pending upload keeps alive the two tallies alone, never that step's record. */
103
+ export declare function mergeWhenSettled(into: OffloadTally, from: OffloadTally, settled: Promise<void>[]): Promise<void>;
104
+ /** The uploads an export queued after placing its records, and what they came to once settled:
105
+ * each record's own are added when its placement returns. */
106
+ export interface LateUploads {
107
+ tally: OffloadTally;
108
+ settled: Promise<void>[];
109
+ /** Told of each upload as it is queued: when it settles, how many of the bytes it charges its
110
+ * record holds too (none for bytes decoded or rewritten apart from the record), and when that
111
+ * charge is released. */
112
+ queued?(settled: Promise<void>, shared: number, released: Promise<void>): void;
113
+ }
87
114
  /** A media type accepted by the upload contract. */
88
115
  export declare function isBlobContentType(value: unknown): value is string;
89
116
  /** The media type an uploaded text is stored under: JSON when it reads as a JSON object or array
@@ -106,8 +133,9 @@ export declare function digestOf(value: string | Uint8Array | ByteSource, deadli
106
133
  }>;
107
134
  /** The `hue.blobs` element for one uploaded value. */
108
135
  export declare function blobEntry(key: string, sha256: string, size: number, contentType: string, name?: string): string;
109
- /** What one export's uploads share: their deadline, and the export's hold for a receiver that
110
- * limited the rate (`wait` resolves false when the hold may not take `millis` more). */
136
+ /** What an upload may spend: its deadline, and its wait before another attempt (`wait` resolves
137
+ * false when the wait was ended early or may not be taken). An export's own context bounds its
138
+ * hashing only. */
111
139
  export interface UploadContext {
112
140
  deadline: number;
113
141
  wait(millis: number): Promise<boolean>;
@@ -132,50 +160,97 @@ export interface UploaderOptions {
132
160
  }
133
161
  /**
134
162
  * Uploads values to Hue: reserves each with `POST /api/v1/otlp/blobs`, PUTs its bytes to the
135
- * presigned URL Hue answers (skipped when Hue already stores the value), and completes it. At
136
- * most {@link UPLOAD_CONCURRENCY} uploads run at once; every request has a deadline and none
137
- * follows a redirect. A receiver without the route (a 404 without Hue's JSON error, a 405 or
138
- * 501, or a 200 that is no reservation) is remembered for {@link UNSUPPORTED_PAUSE_MILLIS}, and a
139
- * transient failure or a refusal that is not about the value alone pauses uploads for
140
- * {@link FAILURE_PAUSE_MILLIS}, so values fall back to the inline cut instead of retrying.
163
+ * presigned URL Hue answers (skipped when Hue already stores the value), and completes it. An
164
+ * export queues its values ({@link enqueue}) and sends its records without waiting for them; the
165
+ * import stores each value before it sends its span ({@link store}). At most
166
+ * {@link UPLOAD_CONCURRENCY} uploads run at once, and {@link HASH_CONCURRENCY} hashes apart from
167
+ * them. A reservation or PUT that gets no answer, a 408, a 429 or a 5xx is tried
168
+ * {@link UPLOAD_ATTEMPTS} times; every request has a deadline and none follows a redirect. A
169
+ * receiver without the route (a 404 without Hue's JSON error, a 405 or 501, or a 200 that is no
170
+ * reservation) is remembered for {@link UNSUPPORTED_PAUSE_MILLIS}: values exported meanwhile keep
171
+ * their fallback, never an entry the receiver cannot resolve.
141
172
  */
142
173
  export declare class BlobUploader {
143
174
  private readonly options;
175
+ /** Until when the receiver is taken to lack the upload route. */
144
176
  private unsupportedUntil;
145
- private pausedUntil;
146
177
  private closed;
147
- private running;
148
- private waiting;
178
+ private uploads;
179
+ private hashes;
180
+ private queued;
181
+ private queuedBytes;
182
+ /** What queued uploads keep alive, each charged once, from the first upload holding it until
183
+ * the last settles: two files read in place from one recorded string charge it once, whole. */
184
+ private holding;
185
+ private sleeps;
149
186
  private requests;
150
187
  private outcomes;
188
+ /** The first wait before another attempt, doubled each time; tests shorten it. */
189
+ private retryDelayMillis;
190
+ /** How long a queued upload may take; tests shorten it. */
191
+ private lifetimeMillis;
151
192
  constructor(options: UploaderOptions);
152
193
  /** Whether a value over the inline limit is worth holding whole for upload now. */
153
194
  available(): boolean;
154
195
  /** Whether the receiver lacks the upload route, as it last answered. */
155
196
  unsupported(): boolean;
156
- /** Ends uploads in flight and refuses new ones: their values fall back to the inline cut. */
197
+ /** Ends uploads in flight, queued uploads and their waits, and refuses new ones: values not
198
+ * yet exported fall back to the inline cut, and uploads queued by exported records fail. */
157
199
  close(): void;
158
200
  /** Runs `work` once fewer than {@link UPLOAD_CONCURRENCY} uploads are in flight. */
159
201
  limit<R>(work: () => Promise<R>): Promise<R>;
202
+ /** The SHA-256 and size of `value` (see {@link digestOf}), hashed in a place of its own: an
203
+ * export never waits for an upload to hash its values. */
204
+ digest(value: string | Uint8Array | ByteSource, deadline: number): Promise<FileDigest>;
205
+ /** The bytes queued uploads keep alive until they settle. */
206
+ pendingBytes(): number;
160
207
  /** Why a value cannot be uploaded right now without trying, if it cannot. */
161
208
  refusal(): FallbackReason | undefined;
162
- private pause;
209
+ /** Why the value cannot be uploaded at all, whatever Hue would answer. */
210
+ private invalid;
211
+ /**
212
+ * Queues `value`'s upload for an exported record, deciding now what the record lists: a value
213
+ * refused without trying (see {@link refusal}), invalid, or past the queue's
214
+ * {@link MAX_QUEUED_UPLOADS} uploads or {@link MAX_HELD_BLOB_BYTES} keeps its fallback. A queued
215
+ * upload runs within {@link uploadLifetimeMillis} of now, holding `value` until it settles;
216
+ * `done` never rejects. `bytes` is what it adds to {@link pendingBytes}: the strings it keeps
217
+ * alive that no other queued upload holds, charged until `released`, once the last upload
218
+ * holding them settles.
219
+ */
220
+ enqueue(traceId: string, key: string, value: string | Uint8Array | ByteSource, contentType: string, digest: FileDigest): {
221
+ ok: false;
222
+ reason: FallbackReason;
223
+ } | {
224
+ ok: true;
225
+ done: Promise<Outcome>;
226
+ bytes: number;
227
+ released: Promise<void>;
228
+ };
229
+ /** Waits `millis`; false when {@link close} ended the wait first. The timer keeps the process
230
+ * running, as an export in progress does, within the upload's lifetime. */
231
+ sleep(millis: number): Promise<boolean>;
232
+ /** Waits before attempt `attempt + 1`: `Retry-After` when the answer gave one, otherwise a
233
+ * second doubled each attempt with jitter, at most a minute. The upload's place is given up
234
+ * meanwhile, so uploads queued behind it run. False when the wait would pass the upload's
235
+ * deadline, was ended, or no place came free again before the deadline. */
236
+ private retryWait;
163
237
  /**
164
238
  * Stores `value` under `key` of the trace: reserved, uploaded unless Hue already holds it, and
165
239
  * completed. Only a fresh upload takes a {@link limit} slot: another reference to bytes the
166
240
  * trace is already storing shares that upload's outcome without one, so repeated files never
167
- * hold the slots new files need. The caller must not hold a slot.
241
+ * hold the slots new files need. The caller must not hold a slot. Never rejects.
168
242
  */
169
243
  store(traceId: string, key: string, value: string | Uint8Array | ByteSource, contentType: string, digest: FileDigest, upload: UploadContext): Promise<Outcome>;
170
244
  private storeFresh;
171
245
  private jsonHeaders;
172
- /** Reserves the value: Hue answers that it holds it already, or a presigned PUT. A rate limit
173
- * is waited out within the export's hold, a transient refusal retried once. */
246
+ /** Reserves the value: Hue answers that it holds it already, or a presigned PUT. No answer, a
247
+ * 408, a 429 or a 5xx other than 501 is tried again, after its `Retry-After` when it gave one,
248
+ * within the upload's deadline. */
174
249
  private reserve;
175
- private delay;
176
250
  /** A 200 answer read: `exists`, or an upload whose URL and headers are usable as given. */
177
251
  private reservation;
178
- /** The PUT of the value's bytes, with exactly the headers Hue signed. A source that no longer
252
+ /** The PUT of the value's bytes, with exactly the headers Hue signed. No answer, a 408, a 429 or
253
+ * a 5xx is transient, with the answer's `Retry-After` when it gave one. A source that no longer
179
254
  * holds its bytes (a file changed since it was recorded) ends the request before its body is
180
255
  * complete, and fails the value without a retry. */
181
256
  private put;
@@ -187,34 +262,39 @@ export declare class BlobUploader {
187
262
  */
188
263
  private request;
189
264
  }
190
- /** Where one export's values are uploaded, and what it learned of them. */
265
+ /** Where one record's values are uploaded, and what it learned of them. */
191
266
  export interface OffloadOptions {
192
267
  uploader: BlobUploader;
193
268
  traceId: string;
194
269
  /** The receiver's inline value limit. */
195
270
  valueBytes: number;
271
+ /** The deadline for hashing, and for a blocking store (no `background`) its uploads too. */
196
272
  upload: UploadContext;
197
273
  tally: OffloadTally;
198
274
  memo?: MediaDigestMemo;
199
275
  /** For tests: the length past which a long string is read in place (`LONG_STRING_UNITS`). */
200
276
  longUnits?: number;
277
+ /** Set by an export: values are placed as uploaded and their uploads queued, each counted in
278
+ * `background.tally` once it settles; `tally` counts the values refused when placed. Absent
279
+ * for the import, which stores each value, under the type Hue stores it as, before placing it. */
280
+ background?: LateUploads;
201
281
  }
202
282
  /**
203
283
  * A span's own attributes with each candidate placed: binary media becomes a digest, while
204
- * text media over 64 KiB retains a 16 KiB prefix after upload or a digest on failure.
205
- * A value still over the inline limit is uploaded and replaced by its first
206
- * 16 KiB (or cut to the limit), each upload listed under `hue.blobs`, each whole value cut or
284
+ * text media over 64 KiB retains a 16 KiB prefix once uploaded or queued, or a digest when
285
+ * refused. A value still over the inline limit is uploaded and replaced by its first 16 KiB (or
286
+ * cut to the limit when refused), each upload listed under `hue.blobs`, each whole value cut or
207
287
  * uploaded listed under `hue.truncated`. Every fallback is counted in the tally.
208
288
  */
209
289
  export declare function offloadAttributes(attributes: Record<string, unknown>, collector: OffloadCollector, options: OffloadOptions): Promise<Record<string, unknown>>;
210
290
  /**
211
291
  * A record's own attributes, and a log record's body, with each candidate placed (see
212
- * {@link offloadAttributes}). A file is uploaded and placed in its digest form (a text file in a
213
- * provider part keeps a 16 KiB prefix), its `hue.blobs` entry keyed `<attribute>#<pointer>` or, in
214
- * a log record's body, `body#<pointer>`. A file that is not uploaded keeps its bytes inline, except
215
- * in a span's own recorded messages, where it keeps its digest as before. A log record's values
216
- * over the inline limit are cut, never uploaded whole, and a body still over it is replaced by the
217
- * receiver's marker, as before.
292
+ * {@link offloadAttributes}). A file is uploaded (or, for an export, queued) and placed in its
293
+ * digest form (a text file in a provider part keeps a 16 KiB prefix), its `hue.blobs` entry keyed
294
+ * `<attribute>#<pointer>` or, in a log record's body, `body#<pointer>`. A file refused or not
295
+ * uploaded before it is placed keeps its bytes inline, except in a span's own recorded messages,
296
+ * where it keeps its digest as before. A log record's values over the inline limit are cut, never
297
+ * uploaded whole, and a body still over it is replaced by the receiver's marker, as before.
218
298
  */
219
299
  export declare function offloadRecord(attributes: Record<string, unknown>, collector: OffloadCollector, options: OffloadOptions): Promise<{
220
300
  attributes: Record<string, unknown>;