@slates/provider 1.0.0-rc.18 → 1.0.0-rc.19

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.
@@ -1,17 +1,98 @@
1
+ import { Readable } from 'node:stream';
2
+ import PQueue from 'p-queue';
3
+ import {
4
+ createBase64Attachment,
5
+ createUrlAttachment,
6
+ type SlateAttachment
7
+ } from '../action/attachment';
8
+ import { type SlateLiveInvocationInfo, uploadAttachmentDirect } from '../action/directUpload';
1
9
  import type { SlateHttpTrace } from '../axios/trace';
2
10
  import type { SlateLogger, SlateLogMessageInput } from '../logger';
3
11
  import type { SlateSpecification } from '../specification/specification';
4
12
 
13
+ export type { SlateLiveInvocationInfo };
14
+
15
+ export type SlateAddAttachmentContent =
16
+ | Buffer
17
+ | Uint8Array
18
+ | ArrayBuffer
19
+ | ReadableStream<Uint8Array>
20
+ | NodeJS.ReadableStream
21
+ | Response;
22
+
23
+ export type SlateAddAttachmentInput =
24
+ | {
25
+ type: 'url';
26
+ url: string | URL;
27
+ mimeType?: string;
28
+ filename?: string;
29
+ headers?: Record<string, string>;
30
+ query?: Record<string, string>;
31
+ refreshReference?: unknown;
32
+ refreshAt?: string;
33
+ }
34
+ | {
35
+ type: 'content';
36
+ content: SlateAddAttachmentContent;
37
+ mimeType?: string;
38
+ filename?: string;
39
+ };
40
+
41
+ let isBufferLike = (value: unknown): value is Buffer | Uint8Array | ArrayBuffer =>
42
+ value instanceof Uint8Array || value instanceof ArrayBuffer;
43
+
44
+ let toUint8Array = (value: Buffer | Uint8Array | ArrayBuffer) =>
45
+ value instanceof ArrayBuffer ? new Uint8Array(value) : value;
46
+
47
+ let isReadableStream = (value: unknown): value is ReadableStream<Uint8Array> =>
48
+ typeof ReadableStream !== 'undefined' && value instanceof ReadableStream;
49
+
50
+ let isNodeReadable = (value: unknown): value is NodeJS.ReadableStream =>
51
+ !!value &&
52
+ typeof value === 'object' &&
53
+ typeof (value as any).pipe === 'function' &&
54
+ typeof (value as any).on === 'function';
55
+
56
+ let isResponseLike = (value: unknown): value is Response =>
57
+ typeof Response !== 'undefined' && value instanceof Response;
58
+
59
+ let readStreamToBuffer = async (stream: ReadableStream<Uint8Array>): Promise<Buffer> => {
60
+ let reader = stream.getReader();
61
+ let chunks: Uint8Array[] = [];
62
+
63
+ while (true) {
64
+ let { done, value } = await reader.read();
65
+ if (done) break;
66
+ if (value) chunks.push(value);
67
+ }
68
+
69
+ return Buffer.concat(chunks);
70
+ };
71
+
72
+ interface NormalizedStreamInput {
73
+ stream: ReadableStream<Uint8Array>;
74
+ mimeType?: string;
75
+ filename?: string;
76
+ }
77
+
5
78
  export class SlatePublicContext<InputType extends {}> {
6
79
  #input: InputType;
7
80
  #httpTraces: SlateHttpTrace[] = [];
8
81
 
82
+ #liveInvocation: SlateLiveInvocationInfo | null;
83
+ #attachmentsDisabled = false;
84
+ #attachments: SlateAttachment[] = [];
85
+ #pendingUploads: Promise<void>[] = [];
86
+ #uploadQueue = new PQueue({ concurrency: 10 });
87
+
9
88
  constructor(
10
89
  input: InputType,
11
90
  private readonly spec: SlateSpecification<any, any>,
12
- private readonly logger: SlateLogger
91
+ private readonly logger: SlateLogger,
92
+ liveInvocation: SlateLiveInvocationInfo | null = null
13
93
  ) {
14
94
  this.#input = input;
95
+ this.#liveInvocation = liveInvocation;
15
96
  }
16
97
 
17
98
  get specification() {
@@ -86,6 +167,110 @@ export class SlatePublicContext<InputType extends {}> {
86
167
  progress(message: SlateLogMessageInput) {
87
168
  this.logger.progress(message);
88
169
  }
170
+
171
+ async addAttachment(input: SlateAddAttachmentInput): Promise<void> {
172
+ if (input.type === 'url') {
173
+ this.#attachments.push(
174
+ createUrlAttachment(input.url.toString(), {
175
+ mimeType: input.mimeType,
176
+ headers: input.headers,
177
+ query: input.query,
178
+ refreshReference: input.refreshReference,
179
+ refreshAt: input.refreshAt
180
+ })
181
+ );
182
+ return;
183
+ }
184
+
185
+ let content = input.content;
186
+
187
+ if (isBufferLike(content)) {
188
+ let bytes = toUint8Array(content);
189
+ this.#attachments.push(
190
+ createBase64Attachment(Buffer.from(bytes).toString('base64'), input.mimeType)
191
+ );
192
+ return;
193
+ }
194
+
195
+ let normalized = await this.#normalizeStreamInput(content, {
196
+ mimeType: input.mimeType,
197
+ filename: input.filename
198
+ });
199
+ if (!normalized) return;
200
+
201
+ if (!this.#liveInvocation || this.#attachmentsDisabled) {
202
+ let buffer = await readStreamToBuffer(normalized.stream);
203
+ this.#attachments.push(
204
+ createBase64Attachment(buffer.toString('base64'), normalized.mimeType)
205
+ );
206
+ return;
207
+ }
208
+
209
+ let live = this.#liveInvocation;
210
+ let task = this.#uploadQueue
211
+ .add(() =>
212
+ uploadAttachmentDirect({
213
+ live,
214
+ mimeType: normalized.mimeType,
215
+ filename: normalized.filename,
216
+ body: normalized.stream
217
+ })
218
+ )
219
+ .then(attachment => {
220
+ this.#attachments.push(attachment);
221
+ })
222
+ .catch(err => {
223
+ this.#attachmentsDisabled = true;
224
+ this.warn({
225
+ message: `Attachment dropped: direct upload failed (${
226
+ err instanceof Error ? err.message : String(err)
227
+ }).`
228
+ });
229
+ });
230
+
231
+ this.#pendingUploads.push(task);
232
+ }
233
+
234
+ async #normalizeStreamInput(
235
+ input: ReadableStream<Uint8Array> | NodeJS.ReadableStream | Response,
236
+ opts: { mimeType?: string; filename?: string }
237
+ ): Promise<NormalizedStreamInput | null> {
238
+ if (isResponseLike(input)) {
239
+ if (!input.body) {
240
+ this.warn({ message: 'Attachment dropped: response has no body.' });
241
+ return null;
242
+ }
243
+ return {
244
+ stream: input.body,
245
+ mimeType: opts.mimeType ?? input.headers.get('content-type') ?? undefined,
246
+ filename: opts.filename
247
+ };
248
+ }
249
+
250
+ if (isReadableStream(input)) {
251
+ return { stream: input, mimeType: opts.mimeType, filename: opts.filename };
252
+ }
253
+
254
+ if (isNodeReadable(input)) {
255
+ return {
256
+ stream: Readable.toWeb(input as any) as unknown as ReadableStream<Uint8Array>,
257
+ mimeType: opts.mimeType,
258
+ filename: opts.filename
259
+ };
260
+ }
261
+
262
+ this.warn({ message: 'Attachment dropped: unsupported attachment input type.' });
263
+ return null;
264
+ }
265
+
266
+ async _finalizeAttachments(): Promise<SlateAttachment[]> {
267
+ await Promise.allSettled(this.#pendingUploads);
268
+ return this.#attachments;
269
+ }
270
+
271
+ _getAuthConfigForRedaction(): Record<string, unknown> | undefined {
272
+ return undefined;
273
+ }
89
274
  }
90
275
 
91
276
  export class SlateContext<
@@ -101,9 +286,10 @@ export class SlateContext<
101
286
  input: InputType,
102
287
  auth: AuthType,
103
288
  spec: SlateSpecification<ConfigType, AuthType>,
104
- logger: SlateLogger
289
+ logger: SlateLogger,
290
+ liveInvocation: SlateLiveInvocationInfo | null = null
105
291
  ) {
106
- super(input, spec, logger);
292
+ super(input, spec, logger, liveInvocation);
107
293
  this.#config = config;
108
294
  this.#auth = auth;
109
295
  }
@@ -115,4 +301,8 @@ export class SlateContext<
115
301
  get auth() {
116
302
  return Object.freeze(this.#auth);
117
303
  }
304
+
305
+ override _getAuthConfigForRedaction(): Record<string, unknown> | undefined {
306
+ return this.#auth as Record<string, unknown>;
307
+ }
118
308
  }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './action';
2
2
  export * from './action/attachment';
3
+ export * from './action/directUpload';
3
4
  export * from './adapter';
4
5
  export * from './auth';
5
6
  export * from './axios';