@masters-union/union-stack 0.3.8 → 0.3.10
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 +42 -16
- package/dist/cdn/loader.v1.global.js +4 -2
- package/dist/cdn/loader.v1.global.js.map +1 -1
- package/dist/{chunk-TNWAUSLA.js → chunk-F4EVNUJZ.cjs} +10 -140
- package/dist/chunk-F4EVNUJZ.cjs.map +1 -0
- package/dist/chunk-IIVRPVNX.js +140 -0
- package/dist/chunk-IIVRPVNX.js.map +1 -0
- package/dist/chunk-IV76YPDA.cjs +142 -0
- package/dist/chunk-IV76YPDA.cjs.map +1 -0
- package/dist/{chunk-ACQDEKCW.cjs → chunk-MFZ2JGLP.js} +6 -142
- package/dist/chunk-MFZ2JGLP.js.map +1 -0
- package/dist/client-C0qMOfb3.d.ts +112 -0
- package/dist/client-DOTa3Mai.d.cts +112 -0
- package/dist/index.cjs +4 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/node.cjs +244 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.cts +56 -0
- package/dist/node.d.ts +56 -0
- package/dist/node.js +221 -0
- package/dist/node.js.map +1 -0
- package/dist/picker.cjs +213 -11
- package/dist/picker.cjs.map +1 -1
- package/dist/picker.d.cts +10 -2
- package/dist/picker.d.ts +10 -2
- package/dist/picker.js +213 -11
- package/dist/picker.js.map +1 -1
- package/dist/react.cjs +3 -2
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +4 -2
- package/dist/react.d.ts +4 -2
- package/dist/react.js +2 -1
- package/dist/react.js.map +1 -1
- package/dist/{client-I6TadQ-m.d.cts → types-ZCfjNZls.d.cts} +20 -93
- package/dist/{client-I6TadQ-m.d.ts → types-ZCfjNZls.d.ts} +20 -93
- package/package.json +7 -1
- package/dist/chunk-ACQDEKCW.cjs.map +0 -1
- package/dist/chunk-TNWAUSLA.js.map +0 -1
package/dist/node.cjs
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkF4EVNUJZ_cjs = require('./chunk-F4EVNUJZ.cjs');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var promises = require('fs/promises');
|
|
6
|
+
var fs = require('fs');
|
|
7
|
+
|
|
8
|
+
function _interopNamespace(e) {
|
|
9
|
+
if (e && e.__esModule) return e;
|
|
10
|
+
var n = Object.create(null);
|
|
11
|
+
if (e) {
|
|
12
|
+
Object.keys(e).forEach(function (k) {
|
|
13
|
+
if (k !== 'default') {
|
|
14
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
15
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: function () { return e[k]; }
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
n.default = e;
|
|
23
|
+
return Object.freeze(n);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
|
|
27
|
+
|
|
28
|
+
var EXT_TO_MIME = {
|
|
29
|
+
// images
|
|
30
|
+
png: "image/png",
|
|
31
|
+
jpg: "image/jpeg",
|
|
32
|
+
jpeg: "image/jpeg",
|
|
33
|
+
gif: "image/gif",
|
|
34
|
+
webp: "image/webp",
|
|
35
|
+
avif: "image/avif",
|
|
36
|
+
svg: "image/svg+xml",
|
|
37
|
+
bmp: "image/bmp",
|
|
38
|
+
ico: "image/x-icon",
|
|
39
|
+
heic: "image/heic",
|
|
40
|
+
tiff: "image/tiff",
|
|
41
|
+
tif: "image/tiff",
|
|
42
|
+
// documents
|
|
43
|
+
pdf: "application/pdf",
|
|
44
|
+
txt: "text/plain",
|
|
45
|
+
csv: "text/csv",
|
|
46
|
+
md: "text/markdown",
|
|
47
|
+
json: "application/json",
|
|
48
|
+
xml: "application/xml",
|
|
49
|
+
html: "text/html",
|
|
50
|
+
htm: "text/html",
|
|
51
|
+
// office
|
|
52
|
+
doc: "application/msword",
|
|
53
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
54
|
+
xls: "application/vnd.ms-excel",
|
|
55
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
56
|
+
ppt: "application/vnd.ms-powerpoint",
|
|
57
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
58
|
+
// media
|
|
59
|
+
mp4: "video/mp4",
|
|
60
|
+
webm: "video/webm",
|
|
61
|
+
mov: "video/quicktime",
|
|
62
|
+
mkv: "video/x-matroska",
|
|
63
|
+
mp3: "audio/mpeg",
|
|
64
|
+
wav: "audio/wav",
|
|
65
|
+
ogg: "audio/ogg",
|
|
66
|
+
m4a: "audio/mp4",
|
|
67
|
+
// archives
|
|
68
|
+
zip: "application/zip",
|
|
69
|
+
gz: "application/gzip",
|
|
70
|
+
tar: "application/x-tar"
|
|
71
|
+
};
|
|
72
|
+
function mimeFromName(name) {
|
|
73
|
+
const ext = path.extname(name).slice(1).toLowerCase();
|
|
74
|
+
return EXT_TO_MIME[ext] || "application/octet-stream";
|
|
75
|
+
}
|
|
76
|
+
var openAsBlob2 = fs__namespace.openAsBlob;
|
|
77
|
+
function toBlobPart(data) {
|
|
78
|
+
return data;
|
|
79
|
+
}
|
|
80
|
+
function isReadable(x) {
|
|
81
|
+
return !!x && typeof x.pipe === "function" && typeof x.read === "function";
|
|
82
|
+
}
|
|
83
|
+
async function streamToBuffer(stream) {
|
|
84
|
+
const chunks = [];
|
|
85
|
+
for await (const chunk of stream) {
|
|
86
|
+
chunks.push(
|
|
87
|
+
Buffer.isBuffer(chunk) ? chunk : typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return Buffer.concat(chunks);
|
|
91
|
+
}
|
|
92
|
+
async function resolveSource(source, opts = {}) {
|
|
93
|
+
if (typeof source === "string") {
|
|
94
|
+
const filename = opts.filename || path.basename(source) || "untitled";
|
|
95
|
+
const mimetype = opts.mimeType || mimeFromName(filename);
|
|
96
|
+
try {
|
|
97
|
+
const blob = openAsBlob2 ? await openAsBlob2(source, { type: mimetype }) : new Blob([toBlobPart(await promises.readFile(source))], { type: mimetype });
|
|
98
|
+
return { blob, filename, mimetype };
|
|
99
|
+
} catch (cause) {
|
|
100
|
+
throw chunkF4EVNUJZ_cjs.makeError("VALIDATION", `Could not read file at "${source}".`, {
|
|
101
|
+
retryable: false,
|
|
102
|
+
cause
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (typeof Blob !== "undefined" && source instanceof Blob) {
|
|
107
|
+
const filename = opts.filename || source.name || "untitled";
|
|
108
|
+
const mimetype = opts.mimeType || source.type || mimeFromName(filename);
|
|
109
|
+
const blob = opts.mimeType && opts.mimeType !== source.type ? new Blob([source], { type: opts.mimeType }) : source;
|
|
110
|
+
return { blob, filename, mimetype };
|
|
111
|
+
}
|
|
112
|
+
if (isReadable(source)) {
|
|
113
|
+
const filename = opts.filename || "untitled";
|
|
114
|
+
const mimetype = opts.mimeType || mimeFromName(filename);
|
|
115
|
+
const buf = await streamToBuffer(source);
|
|
116
|
+
return { blob: new Blob([toBlobPart(buf)], { type: mimetype }), filename, mimetype };
|
|
117
|
+
}
|
|
118
|
+
if (source instanceof ArrayBuffer || ArrayBuffer.isView(source)) {
|
|
119
|
+
const filename = opts.filename || "untitled";
|
|
120
|
+
const mimetype = opts.mimeType || mimeFromName(filename);
|
|
121
|
+
return { blob: new Blob([toBlobPart(source)], { type: mimetype }), filename, mimetype };
|
|
122
|
+
}
|
|
123
|
+
throw chunkF4EVNUJZ_cjs.makeError(
|
|
124
|
+
"VALIDATION",
|
|
125
|
+
"Unsupported upload source. Pass a file path, Buffer, Uint8Array, ArrayBuffer, Blob, or Readable stream.",
|
|
126
|
+
{ retryable: false }
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/node/index.ts
|
|
131
|
+
var API_BASE = "https://api.unionstack.link/v1";
|
|
132
|
+
var DEFAULT_FILE_CONCURRENCY = 5;
|
|
133
|
+
var UnionStackNodeClient = class {
|
|
134
|
+
constructor(cfg) {
|
|
135
|
+
if (!cfg?.apiKey) {
|
|
136
|
+
throw chunkF4EVNUJZ_cjs.makeError("CONFIG", 'apiKey is required. Pass { apiKey: "unionstack_live_\u2026" }.', {
|
|
137
|
+
retryable: false
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
this.cfg = {
|
|
141
|
+
...cfg,
|
|
142
|
+
apiBase: API_BASE,
|
|
143
|
+
// No picker on the server — never prefetch the picker config.
|
|
144
|
+
skipConfigPrefetch: true,
|
|
145
|
+
// Tag the traffic as server-to-server ("node/…") so cdn-be can tell it
|
|
146
|
+
// apart from browser SDK calls ("js/…").
|
|
147
|
+
sdkHeader: chunkF4EVNUJZ_cjs.NODE_SDK_VERSION_HEADER
|
|
148
|
+
};
|
|
149
|
+
this.uploader = new chunkF4EVNUJZ_cjs.Uploader(this.cfg);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Upload one file. Accepts a path on disk, a Buffer/Uint8Array/ArrayBuffer, a
|
|
153
|
+
* Blob/File, or a Readable stream. Returns the stored file (with its public
|
|
154
|
+
* `url`). Rejects with an {@link UploadError}.
|
|
155
|
+
*/
|
|
156
|
+
async upload(source, opts = {}) {
|
|
157
|
+
const resolved = await resolveSource(source, { filename: opts.filename, mimeType: opts.mimeType });
|
|
158
|
+
return this.uploader.upload(resolved.blob, {
|
|
159
|
+
...opts,
|
|
160
|
+
filename: resolved.filename,
|
|
161
|
+
mimeType: resolved.mimetype
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Upload many files. Runs up to {@link DEFAULT_FILE_CONCURRENCY} files at once
|
|
166
|
+
* and never rejects on a single file's failure — successes and failures come
|
|
167
|
+
* back separately so a partial failure doesn't drop the whole batch.
|
|
168
|
+
*/
|
|
169
|
+
async uploadMany(sources, opts = {}) {
|
|
170
|
+
if (!Array.isArray(sources) || sources.length === 0) {
|
|
171
|
+
const err = chunkF4EVNUJZ_cjs.makeError("VALIDATION", "uploadMany requires a non-empty array of sources.", {
|
|
172
|
+
retryable: false
|
|
173
|
+
});
|
|
174
|
+
opts.onError?.(err);
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
const filesUploaded = [];
|
|
178
|
+
const filesFailed = [];
|
|
179
|
+
let cursor = 0;
|
|
180
|
+
const worker = async () => {
|
|
181
|
+
while (true) {
|
|
182
|
+
const i = cursor++;
|
|
183
|
+
if (i >= sources.length) return;
|
|
184
|
+
let resolved;
|
|
185
|
+
try {
|
|
186
|
+
resolved = await resolveSource(sources[i], {
|
|
187
|
+
filename: opts.filename,
|
|
188
|
+
mimeType: opts.mimeType
|
|
189
|
+
});
|
|
190
|
+
} catch (err) {
|
|
191
|
+
filesFailed.push({ file: describeUnresolved(sources[i], i, opts), error: err });
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const picked = {
|
|
195
|
+
uploadId: String(i),
|
|
196
|
+
filename: resolved.filename,
|
|
197
|
+
mimetype: resolved.mimetype,
|
|
198
|
+
size: resolved.blob.size,
|
|
199
|
+
source: "local"
|
|
200
|
+
};
|
|
201
|
+
try {
|
|
202
|
+
const uploaded = await this.uploader.upload(resolved.blob, {
|
|
203
|
+
...opts,
|
|
204
|
+
filename: resolved.filename,
|
|
205
|
+
mimeType: resolved.mimetype
|
|
206
|
+
});
|
|
207
|
+
uploaded.uploadId = picked.uploadId;
|
|
208
|
+
filesUploaded.push(uploaded);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
filesFailed.push({ file: picked, error: err });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
const pool = Math.max(1, Math.min(DEFAULT_FILE_CONCURRENCY, sources.length));
|
|
215
|
+
await Promise.all(Array.from({ length: pool }, worker));
|
|
216
|
+
const result = { filesUploaded, filesFailed };
|
|
217
|
+
opts.onUploadDone?.(result);
|
|
218
|
+
return result;
|
|
219
|
+
}
|
|
220
|
+
/** Read-only view of the resolved config. */
|
|
221
|
+
get config() {
|
|
222
|
+
return this.cfg;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
function describeUnresolved(source, index, opts) {
|
|
226
|
+
return {
|
|
227
|
+
uploadId: String(index),
|
|
228
|
+
filename: opts.filename || (typeof source === "string" ? path.basename(source) : "untitled"),
|
|
229
|
+
mimetype: opts.mimeType || "application/octet-stream",
|
|
230
|
+
size: 0,
|
|
231
|
+
source: "local"
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
var UnionStack = {
|
|
235
|
+
/** Create a server-side client. `cfg.apiKey` is required. */
|
|
236
|
+
init(cfg) {
|
|
237
|
+
return new UnionStackNodeClient(cfg);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
exports.UnionStack = UnionStack;
|
|
242
|
+
exports.UnionStackNodeClient = UnionStackNodeClient;
|
|
243
|
+
//# sourceMappingURL=node.cjs.map
|
|
244
|
+
//# sourceMappingURL=node.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/node/source.ts","../src/node/index.ts"],"names":["extname","openAsBlob","fs","basename","readFile","makeError","NODE_SDK_VERSION_HEADER","Uploader"],"mappings":"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","file":"node.cjs","sourcesContent":["import { basename, extname } from 'node:path';\nimport { readFile } from 'node:fs/promises';\nimport * as fs from 'node:fs';\nimport { Readable } from 'node:stream';\nimport { makeError } from '../errors.js';\n\n/**\n * Anything the Node client knows how to turn into an upload.\n *\n * • `string` — path to a file on disk (absolute or relative to cwd)\n * • `Buffer` / typed arr — raw bytes already in memory\n * • `ArrayBuffer` — raw bytes\n * • `Blob` / `File` — Web file objects (Node 18+ has `Blob`, 20+ has `File`)\n * • `Readable` — a Node stream (buffered in full before upload, since\n * the multipart protocol needs the total size up front)\n */\nexport type NodeUploadSource =\n | string\n | Buffer\n | Uint8Array\n | ArrayBuffer\n | Blob\n | Readable;\n\nexport interface ResolvedSource {\n /** A Web `Blob` the core Uploader can `.slice()` into multipart chunks. */\n blob: Blob;\n filename: string;\n mimetype: string;\n}\n\n// Smallest map that covers the bulk of real-world server uploads. Anything not\n// listed falls back to application/octet-stream — callers can always override\n// via `{ mimeType }`. Kept deliberately short; this is not a full mime database.\nconst EXT_TO_MIME: Record<string, string> = {\n // images\n png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif',\n webp: 'image/webp', avif: 'image/avif', svg: 'image/svg+xml', bmp: 'image/bmp',\n ico: 'image/x-icon', heic: 'image/heic', tiff: 'image/tiff', tif: 'image/tiff',\n // documents\n pdf: 'application/pdf', txt: 'text/plain', csv: 'text/csv', md: 'text/markdown',\n json: 'application/json', xml: 'application/xml', html: 'text/html', htm: 'text/html',\n // office\n doc: 'application/msword',\n docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n xls: 'application/vnd.ms-excel',\n xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n ppt: 'application/vnd.ms-powerpoint',\n pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n // media\n mp4: 'video/mp4', webm: 'video/webm', mov: 'video/quicktime', mkv: 'video/x-matroska',\n mp3: 'audio/mpeg', wav: 'audio/wav', ogg: 'audio/ogg', m4a: 'audio/mp4',\n // archives\n zip: 'application/zip', gz: 'application/gzip', tar: 'application/x-tar',\n};\n\nfunction mimeFromName(name: string): string {\n const ext = extname(name).slice(1).toLowerCase();\n return EXT_TO_MIME[ext] || 'application/octet-stream';\n}\n\n// `fs.openAsBlob` (Node 19.8+/20+) hands back a Blob backed by the file on disk,\n// so slicing it into parts streams lazily instead of loading the whole file into\n// memory. Typed locally — the global DOM `Blob` is what the Uploader wants, and\n// not every @types/node version exposes openAsBlob.\ntype OpenAsBlob = (path: string, options?: { type?: string }) => Promise<Blob>;\nconst openAsBlob = (fs as unknown as { openAsBlob?: OpenAsBlob }).openAsBlob;\n\n// Node's `Buffer<ArrayBufferLike>` and the DOM lib's `BlobPart`\n// (`ArrayBufferView<ArrayBuffer>`) don't line up under recent @types/node, even\n// though every value passed here is a valid Blob part at runtime. Cast once.\nfunction toBlobPart(data: ArrayBuffer | ArrayBufferView): BlobPart {\n return data as unknown as BlobPart;\n}\n\nfunction isReadable(x: unknown): x is Readable {\n return (\n !!x &&\n typeof (x as Readable).pipe === 'function' &&\n typeof (x as { read?: unknown }).read === 'function'\n );\n}\n\nasync function streamToBuffer(stream: Readable): Promise<Buffer> {\n const chunks: Buffer[] = [];\n for await (const chunk of stream) {\n chunks.push(\n Buffer.isBuffer(chunk)\n ? chunk\n : typeof chunk === 'string'\n ? Buffer.from(chunk)\n : Buffer.from(chunk as Uint8Array),\n );\n }\n return Buffer.concat(chunks);\n}\n\n/**\n * Normalize any {@link NodeUploadSource} into a `{ blob, filename, mimetype }`\n * the core Uploader can consume. Caller-supplied `filename` / `mimeType` always\n * win over what we infer from the path or the blob.\n */\nexport async function resolveSource(\n source: NodeUploadSource,\n opts: { filename?: string; mimeType?: string } = {},\n): Promise<ResolvedSource> {\n // 1. File path on disk.\n if (typeof source === 'string') {\n const filename = opts.filename || basename(source) || 'untitled';\n const mimetype = opts.mimeType || mimeFromName(filename);\n try {\n const blob = openAsBlob\n ? await openAsBlob(source, { type: mimetype })\n : new Blob([toBlobPart(await readFile(source))], { type: mimetype });\n return { blob, filename, mimetype };\n } catch (cause) {\n throw makeError('VALIDATION', `Could not read file at \"${source}\".`, {\n retryable: false,\n cause,\n });\n }\n }\n\n // 2. Already a Blob / File.\n if (typeof Blob !== 'undefined' && source instanceof Blob) {\n const filename = opts.filename || (source as Blob & { name?: string }).name || 'untitled';\n const mimetype = opts.mimeType || source.type || mimeFromName(filename);\n // Only re-wrap when we need to stamp a type the blob doesn't already carry.\n const blob =\n opts.mimeType && opts.mimeType !== source.type\n ? new Blob([source], { type: opts.mimeType })\n : source;\n return { blob, filename, mimetype };\n }\n\n // 3. Node Readable stream — buffer it fully; multipart needs the size up front.\n if (isReadable(source)) {\n const filename = opts.filename || 'untitled';\n const mimetype = opts.mimeType || mimeFromName(filename);\n const buf = await streamToBuffer(source);\n return { blob: new Blob([toBlobPart(buf)], { type: mimetype }), filename, mimetype };\n }\n\n // 4. Buffer / Uint8Array / ArrayBuffer (Buffer is an ArrayBuffer view).\n if (source instanceof ArrayBuffer || ArrayBuffer.isView(source)) {\n const filename = opts.filename || 'untitled';\n const mimetype = opts.mimeType || mimeFromName(filename);\n return { blob: new Blob([toBlobPart(source)], { type: mimetype }), filename, mimetype };\n }\n\n throw makeError(\n 'VALIDATION',\n 'Unsupported upload source. Pass a file path, Buffer, Uint8Array, ArrayBuffer, Blob, or Readable stream.',\n { retryable: false },\n );\n}\n","import { basename } from 'node:path';\nimport { Uploader } from '../uploader.js';\nimport { makeError } from '../errors.js';\nimport { NODE_SDK_VERSION_HEADER } from '../version.js';\nimport { resolveSource, type NodeUploadSource } from './source.js';\nimport type {\n ClientConfig,\n ResolvedClientConfig,\n UploadOptions,\n BatchUploadOptions,\n UploadedFile,\n PickedFile,\n PickResponse,\n UploadError,\n} from '../types.js';\n\n/**\n * UnionStack API URL baked into the SDK. Consumers never see or set this — we\n * ship a new SDK version if the URL ever changes. (Same constant as the browser\n * client; kept independent so the Node entry doesn't pull in client.ts.)\n */\nconst API_BASE = 'https://api.unionstack.link/v1';\n\n// File-level fan-out for uploadMany. Each file still runs its own part-level\n// concurrency (opts.concurrency), so this caps how many files upload at once —\n// keeps a 10k-file batch from opening 10k sockets / file handles simultaneously.\nconst DEFAULT_FILE_CONCURRENCY = 5;\n\n/**\n * Server-side UnionStack client for Node.js. Uploads files straight to your\n * UnionStack bucket from a backend — no browser, no picker, no DOM.\n *\n * Use an API key whose allowed origins include `*` (server-to-server requests\n * carry no `Origin` header). Create the key and add `*` from your dashboard.\n *\n * ```ts\n * import { UnionStack } from '@masters-union/union-stack/node';\n *\n * const client = UnionStack.init({ apiKey: process.env.UNIONSTACK_API_KEY! });\n * const file = await client.upload('./invoice.pdf');\n * console.log(file.url);\n * ```\n */\nexport class UnionStackNodeClient {\n private uploader: Uploader;\n private cfg: ResolvedClientConfig;\n\n constructor(cfg: ClientConfig) {\n // A server SDK can throw on misconfiguration — unlike the browser client,\n // there's no render path to crash.\n if (!cfg?.apiKey) {\n throw makeError('CONFIG', 'apiKey is required. Pass { apiKey: \"unionstack_live_…\" }.', {\n retryable: false,\n });\n }\n this.cfg = {\n ...cfg,\n apiBase: API_BASE,\n // No picker on the server — never prefetch the picker config.\n skipConfigPrefetch: true,\n // Tag the traffic as server-to-server (\"node/…\") so cdn-be can tell it\n // apart from browser SDK calls (\"js/…\").\n sdkHeader: NODE_SDK_VERSION_HEADER,\n };\n this.uploader = new Uploader(this.cfg);\n }\n\n /**\n * Upload one file. Accepts a path on disk, a Buffer/Uint8Array/ArrayBuffer, a\n * Blob/File, or a Readable stream. Returns the stored file (with its public\n * `url`). Rejects with an {@link UploadError}.\n */\n async upload(source: NodeUploadSource, opts: UploadOptions = {}): Promise<UploadedFile> {\n const resolved = await resolveSource(source, { filename: opts.filename, mimeType: opts.mimeType });\n return this.uploader.upload(resolved.blob, {\n ...opts,\n filename: resolved.filename,\n mimeType: resolved.mimetype,\n });\n }\n\n /**\n * Upload many files. Runs up to {@link DEFAULT_FILE_CONCURRENCY} files at once\n * and never rejects on a single file's failure — successes and failures come\n * back separately so a partial failure doesn't drop the whole batch.\n */\n async uploadMany(sources: NodeUploadSource[], opts: BatchUploadOptions = {}): Promise<PickResponse> {\n if (!Array.isArray(sources) || sources.length === 0) {\n const err = makeError('VALIDATION', 'uploadMany requires a non-empty array of sources.', {\n retryable: false,\n });\n opts.onError?.(err);\n throw err;\n }\n\n const filesUploaded: UploadedFile[] = [];\n const filesFailed: Array<{ file: PickedFile; error: UploadError }> = [];\n\n let cursor = 0;\n const worker = async (): Promise<void> => {\n while (true) {\n const i = cursor++;\n if (i >= sources.length) return;\n\n let resolved;\n try {\n resolved = await resolveSource(sources[i], {\n filename: opts.filename,\n mimeType: opts.mimeType,\n });\n } catch (err) {\n filesFailed.push({ file: describeUnresolved(sources[i], i, opts), error: err as UploadError });\n continue;\n }\n\n const picked: PickedFile = {\n uploadId: String(i),\n filename: resolved.filename,\n mimetype: resolved.mimetype,\n size: resolved.blob.size,\n source: 'local',\n };\n try {\n const uploaded = await this.uploader.upload(resolved.blob, {\n ...opts,\n filename: resolved.filename,\n mimeType: resolved.mimetype,\n });\n uploaded.uploadId = picked.uploadId;\n filesUploaded.push(uploaded);\n } catch (err) {\n filesFailed.push({ file: picked, error: err as UploadError });\n }\n }\n };\n\n const pool = Math.max(1, Math.min(DEFAULT_FILE_CONCURRENCY, sources.length));\n await Promise.all(Array.from({ length: pool }, worker));\n\n const result: PickResponse = { filesUploaded, filesFailed };\n opts.onUploadDone?.(result);\n return result;\n }\n\n /** Read-only view of the resolved config. */\n get config(): Readonly<ResolvedClientConfig> {\n return this.cfg;\n }\n}\n\n/** Best-effort descriptor for a file that failed before we could read it. */\nfunction describeUnresolved(\n source: NodeUploadSource,\n index: number,\n opts: BatchUploadOptions,\n): PickedFile {\n return {\n uploadId: String(index),\n filename: opts.filename || (typeof source === 'string' ? basename(source) : 'untitled'),\n mimetype: opts.mimeType || 'application/octet-stream',\n size: 0,\n source: 'local',\n };\n}\n\nexport const UnionStack = {\n /** Create a server-side client. `cfg.apiKey` is required. */\n init(cfg: ClientConfig): UnionStackNodeClient {\n return new UnionStackNodeClient(cfg);\n },\n};\n\nexport type { NodeUploadSource } from './source.js';\nexport type {\n ClientConfig,\n UploadOptions,\n BatchUploadOptions,\n PickedFile,\n UploadedFile,\n ProgressEvent,\n PickResponse,\n UploadError,\n UploadErrorCode,\n Source,\n} from '../types.js';\n"]}
|
package/dist/node.d.cts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
import { C as ClientConfig, d as UploadOptions, e as UploadedFile, B as BatchUploadOptions, P as PickResponse, R as ResolvedClientConfig } from './types-ZCfjNZls.cjs';
|
|
3
|
+
export { a as PickedFile, b as ProgressEvent, S as Source, U as UploadError, c as UploadErrorCode } from './types-ZCfjNZls.cjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Anything the Node client knows how to turn into an upload.
|
|
7
|
+
*
|
|
8
|
+
* • `string` — path to a file on disk (absolute or relative to cwd)
|
|
9
|
+
* • `Buffer` / typed arr — raw bytes already in memory
|
|
10
|
+
* • `ArrayBuffer` — raw bytes
|
|
11
|
+
* • `Blob` / `File` — Web file objects (Node 18+ has `Blob`, 20+ has `File`)
|
|
12
|
+
* • `Readable` — a Node stream (buffered in full before upload, since
|
|
13
|
+
* the multipart protocol needs the total size up front)
|
|
14
|
+
*/
|
|
15
|
+
type NodeUploadSource = string | Buffer | Uint8Array | ArrayBuffer | Blob | Readable;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Server-side UnionStack client for Node.js. Uploads files straight to your
|
|
19
|
+
* UnionStack bucket from a backend — no browser, no picker, no DOM.
|
|
20
|
+
*
|
|
21
|
+
* Use an API key whose allowed origins include `*` (server-to-server requests
|
|
22
|
+
* carry no `Origin` header). Create the key and add `*` from your dashboard.
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { UnionStack } from '@masters-union/union-stack/node';
|
|
26
|
+
*
|
|
27
|
+
* const client = UnionStack.init({ apiKey: process.env.UNIONSTACK_API_KEY! });
|
|
28
|
+
* const file = await client.upload('./invoice.pdf');
|
|
29
|
+
* console.log(file.url);
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
declare class UnionStackNodeClient {
|
|
33
|
+
private uploader;
|
|
34
|
+
private cfg;
|
|
35
|
+
constructor(cfg: ClientConfig);
|
|
36
|
+
/**
|
|
37
|
+
* Upload one file. Accepts a path on disk, a Buffer/Uint8Array/ArrayBuffer, a
|
|
38
|
+
* Blob/File, or a Readable stream. Returns the stored file (with its public
|
|
39
|
+
* `url`). Rejects with an {@link UploadError}.
|
|
40
|
+
*/
|
|
41
|
+
upload(source: NodeUploadSource, opts?: UploadOptions): Promise<UploadedFile>;
|
|
42
|
+
/**
|
|
43
|
+
* Upload many files. Runs up to {@link DEFAULT_FILE_CONCURRENCY} files at once
|
|
44
|
+
* and never rejects on a single file's failure — successes and failures come
|
|
45
|
+
* back separately so a partial failure doesn't drop the whole batch.
|
|
46
|
+
*/
|
|
47
|
+
uploadMany(sources: NodeUploadSource[], opts?: BatchUploadOptions): Promise<PickResponse>;
|
|
48
|
+
/** Read-only view of the resolved config. */
|
|
49
|
+
get config(): Readonly<ResolvedClientConfig>;
|
|
50
|
+
}
|
|
51
|
+
declare const UnionStack: {
|
|
52
|
+
/** Create a server-side client. `cfg.apiKey` is required. */
|
|
53
|
+
init(cfg: ClientConfig): UnionStackNodeClient;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export { BatchUploadOptions, ClientConfig, type NodeUploadSource, PickResponse, UnionStack, UnionStackNodeClient, UploadOptions, UploadedFile };
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
import { C as ClientConfig, d as UploadOptions, e as UploadedFile, B as BatchUploadOptions, P as PickResponse, R as ResolvedClientConfig } from './types-ZCfjNZls.js';
|
|
3
|
+
export { a as PickedFile, b as ProgressEvent, S as Source, U as UploadError, c as UploadErrorCode } from './types-ZCfjNZls.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Anything the Node client knows how to turn into an upload.
|
|
7
|
+
*
|
|
8
|
+
* • `string` — path to a file on disk (absolute or relative to cwd)
|
|
9
|
+
* • `Buffer` / typed arr — raw bytes already in memory
|
|
10
|
+
* • `ArrayBuffer` — raw bytes
|
|
11
|
+
* • `Blob` / `File` — Web file objects (Node 18+ has `Blob`, 20+ has `File`)
|
|
12
|
+
* • `Readable` — a Node stream (buffered in full before upload, since
|
|
13
|
+
* the multipart protocol needs the total size up front)
|
|
14
|
+
*/
|
|
15
|
+
type NodeUploadSource = string | Buffer | Uint8Array | ArrayBuffer | Blob | Readable;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Server-side UnionStack client for Node.js. Uploads files straight to your
|
|
19
|
+
* UnionStack bucket from a backend — no browser, no picker, no DOM.
|
|
20
|
+
*
|
|
21
|
+
* Use an API key whose allowed origins include `*` (server-to-server requests
|
|
22
|
+
* carry no `Origin` header). Create the key and add `*` from your dashboard.
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { UnionStack } from '@masters-union/union-stack/node';
|
|
26
|
+
*
|
|
27
|
+
* const client = UnionStack.init({ apiKey: process.env.UNIONSTACK_API_KEY! });
|
|
28
|
+
* const file = await client.upload('./invoice.pdf');
|
|
29
|
+
* console.log(file.url);
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
declare class UnionStackNodeClient {
|
|
33
|
+
private uploader;
|
|
34
|
+
private cfg;
|
|
35
|
+
constructor(cfg: ClientConfig);
|
|
36
|
+
/**
|
|
37
|
+
* Upload one file. Accepts a path on disk, a Buffer/Uint8Array/ArrayBuffer, a
|
|
38
|
+
* Blob/File, or a Readable stream. Returns the stored file (with its public
|
|
39
|
+
* `url`). Rejects with an {@link UploadError}.
|
|
40
|
+
*/
|
|
41
|
+
upload(source: NodeUploadSource, opts?: UploadOptions): Promise<UploadedFile>;
|
|
42
|
+
/**
|
|
43
|
+
* Upload many files. Runs up to {@link DEFAULT_FILE_CONCURRENCY} files at once
|
|
44
|
+
* and never rejects on a single file's failure — successes and failures come
|
|
45
|
+
* back separately so a partial failure doesn't drop the whole batch.
|
|
46
|
+
*/
|
|
47
|
+
uploadMany(sources: NodeUploadSource[], opts?: BatchUploadOptions): Promise<PickResponse>;
|
|
48
|
+
/** Read-only view of the resolved config. */
|
|
49
|
+
get config(): Readonly<ResolvedClientConfig>;
|
|
50
|
+
}
|
|
51
|
+
declare const UnionStack: {
|
|
52
|
+
/** Create a server-side client. `cfg.apiKey` is required. */
|
|
53
|
+
init(cfg: ClientConfig): UnionStackNodeClient;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export { BatchUploadOptions, ClientConfig, type NodeUploadSource, PickResponse, UnionStack, UnionStackNodeClient, UploadOptions, UploadedFile };
|
package/dist/node.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { makeError, NODE_SDK_VERSION_HEADER, Uploader } from './chunk-MFZ2JGLP.js';
|
|
2
|
+
import { basename, extname } from 'path';
|
|
3
|
+
import { readFile } from 'fs/promises';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
|
|
6
|
+
var EXT_TO_MIME = {
|
|
7
|
+
// images
|
|
8
|
+
png: "image/png",
|
|
9
|
+
jpg: "image/jpeg",
|
|
10
|
+
jpeg: "image/jpeg",
|
|
11
|
+
gif: "image/gif",
|
|
12
|
+
webp: "image/webp",
|
|
13
|
+
avif: "image/avif",
|
|
14
|
+
svg: "image/svg+xml",
|
|
15
|
+
bmp: "image/bmp",
|
|
16
|
+
ico: "image/x-icon",
|
|
17
|
+
heic: "image/heic",
|
|
18
|
+
tiff: "image/tiff",
|
|
19
|
+
tif: "image/tiff",
|
|
20
|
+
// documents
|
|
21
|
+
pdf: "application/pdf",
|
|
22
|
+
txt: "text/plain",
|
|
23
|
+
csv: "text/csv",
|
|
24
|
+
md: "text/markdown",
|
|
25
|
+
json: "application/json",
|
|
26
|
+
xml: "application/xml",
|
|
27
|
+
html: "text/html",
|
|
28
|
+
htm: "text/html",
|
|
29
|
+
// office
|
|
30
|
+
doc: "application/msword",
|
|
31
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
32
|
+
xls: "application/vnd.ms-excel",
|
|
33
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
34
|
+
ppt: "application/vnd.ms-powerpoint",
|
|
35
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
36
|
+
// media
|
|
37
|
+
mp4: "video/mp4",
|
|
38
|
+
webm: "video/webm",
|
|
39
|
+
mov: "video/quicktime",
|
|
40
|
+
mkv: "video/x-matroska",
|
|
41
|
+
mp3: "audio/mpeg",
|
|
42
|
+
wav: "audio/wav",
|
|
43
|
+
ogg: "audio/ogg",
|
|
44
|
+
m4a: "audio/mp4",
|
|
45
|
+
// archives
|
|
46
|
+
zip: "application/zip",
|
|
47
|
+
gz: "application/gzip",
|
|
48
|
+
tar: "application/x-tar"
|
|
49
|
+
};
|
|
50
|
+
function mimeFromName(name) {
|
|
51
|
+
const ext = extname(name).slice(1).toLowerCase();
|
|
52
|
+
return EXT_TO_MIME[ext] || "application/octet-stream";
|
|
53
|
+
}
|
|
54
|
+
var openAsBlob2 = fs.openAsBlob;
|
|
55
|
+
function toBlobPart(data) {
|
|
56
|
+
return data;
|
|
57
|
+
}
|
|
58
|
+
function isReadable(x) {
|
|
59
|
+
return !!x && typeof x.pipe === "function" && typeof x.read === "function";
|
|
60
|
+
}
|
|
61
|
+
async function streamToBuffer(stream) {
|
|
62
|
+
const chunks = [];
|
|
63
|
+
for await (const chunk of stream) {
|
|
64
|
+
chunks.push(
|
|
65
|
+
Buffer.isBuffer(chunk) ? chunk : typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk)
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return Buffer.concat(chunks);
|
|
69
|
+
}
|
|
70
|
+
async function resolveSource(source, opts = {}) {
|
|
71
|
+
if (typeof source === "string") {
|
|
72
|
+
const filename = opts.filename || basename(source) || "untitled";
|
|
73
|
+
const mimetype = opts.mimeType || mimeFromName(filename);
|
|
74
|
+
try {
|
|
75
|
+
const blob = openAsBlob2 ? await openAsBlob2(source, { type: mimetype }) : new Blob([toBlobPart(await readFile(source))], { type: mimetype });
|
|
76
|
+
return { blob, filename, mimetype };
|
|
77
|
+
} catch (cause) {
|
|
78
|
+
throw makeError("VALIDATION", `Could not read file at "${source}".`, {
|
|
79
|
+
retryable: false,
|
|
80
|
+
cause
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (typeof Blob !== "undefined" && source instanceof Blob) {
|
|
85
|
+
const filename = opts.filename || source.name || "untitled";
|
|
86
|
+
const mimetype = opts.mimeType || source.type || mimeFromName(filename);
|
|
87
|
+
const blob = opts.mimeType && opts.mimeType !== source.type ? new Blob([source], { type: opts.mimeType }) : source;
|
|
88
|
+
return { blob, filename, mimetype };
|
|
89
|
+
}
|
|
90
|
+
if (isReadable(source)) {
|
|
91
|
+
const filename = opts.filename || "untitled";
|
|
92
|
+
const mimetype = opts.mimeType || mimeFromName(filename);
|
|
93
|
+
const buf = await streamToBuffer(source);
|
|
94
|
+
return { blob: new Blob([toBlobPart(buf)], { type: mimetype }), filename, mimetype };
|
|
95
|
+
}
|
|
96
|
+
if (source instanceof ArrayBuffer || ArrayBuffer.isView(source)) {
|
|
97
|
+
const filename = opts.filename || "untitled";
|
|
98
|
+
const mimetype = opts.mimeType || mimeFromName(filename);
|
|
99
|
+
return { blob: new Blob([toBlobPart(source)], { type: mimetype }), filename, mimetype };
|
|
100
|
+
}
|
|
101
|
+
throw makeError(
|
|
102
|
+
"VALIDATION",
|
|
103
|
+
"Unsupported upload source. Pass a file path, Buffer, Uint8Array, ArrayBuffer, Blob, or Readable stream.",
|
|
104
|
+
{ retryable: false }
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/node/index.ts
|
|
109
|
+
var API_BASE = "https://api.unionstack.link/v1";
|
|
110
|
+
var DEFAULT_FILE_CONCURRENCY = 5;
|
|
111
|
+
var UnionStackNodeClient = class {
|
|
112
|
+
constructor(cfg) {
|
|
113
|
+
if (!cfg?.apiKey) {
|
|
114
|
+
throw makeError("CONFIG", 'apiKey is required. Pass { apiKey: "unionstack_live_\u2026" }.', {
|
|
115
|
+
retryable: false
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
this.cfg = {
|
|
119
|
+
...cfg,
|
|
120
|
+
apiBase: API_BASE,
|
|
121
|
+
// No picker on the server — never prefetch the picker config.
|
|
122
|
+
skipConfigPrefetch: true,
|
|
123
|
+
// Tag the traffic as server-to-server ("node/…") so cdn-be can tell it
|
|
124
|
+
// apart from browser SDK calls ("js/…").
|
|
125
|
+
sdkHeader: NODE_SDK_VERSION_HEADER
|
|
126
|
+
};
|
|
127
|
+
this.uploader = new Uploader(this.cfg);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Upload one file. Accepts a path on disk, a Buffer/Uint8Array/ArrayBuffer, a
|
|
131
|
+
* Blob/File, or a Readable stream. Returns the stored file (with its public
|
|
132
|
+
* `url`). Rejects with an {@link UploadError}.
|
|
133
|
+
*/
|
|
134
|
+
async upload(source, opts = {}) {
|
|
135
|
+
const resolved = await resolveSource(source, { filename: opts.filename, mimeType: opts.mimeType });
|
|
136
|
+
return this.uploader.upload(resolved.blob, {
|
|
137
|
+
...opts,
|
|
138
|
+
filename: resolved.filename,
|
|
139
|
+
mimeType: resolved.mimetype
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Upload many files. Runs up to {@link DEFAULT_FILE_CONCURRENCY} files at once
|
|
144
|
+
* and never rejects on a single file's failure — successes and failures come
|
|
145
|
+
* back separately so a partial failure doesn't drop the whole batch.
|
|
146
|
+
*/
|
|
147
|
+
async uploadMany(sources, opts = {}) {
|
|
148
|
+
if (!Array.isArray(sources) || sources.length === 0) {
|
|
149
|
+
const err = makeError("VALIDATION", "uploadMany requires a non-empty array of sources.", {
|
|
150
|
+
retryable: false
|
|
151
|
+
});
|
|
152
|
+
opts.onError?.(err);
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
const filesUploaded = [];
|
|
156
|
+
const filesFailed = [];
|
|
157
|
+
let cursor = 0;
|
|
158
|
+
const worker = async () => {
|
|
159
|
+
while (true) {
|
|
160
|
+
const i = cursor++;
|
|
161
|
+
if (i >= sources.length) return;
|
|
162
|
+
let resolved;
|
|
163
|
+
try {
|
|
164
|
+
resolved = await resolveSource(sources[i], {
|
|
165
|
+
filename: opts.filename,
|
|
166
|
+
mimeType: opts.mimeType
|
|
167
|
+
});
|
|
168
|
+
} catch (err) {
|
|
169
|
+
filesFailed.push({ file: describeUnresolved(sources[i], i, opts), error: err });
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const picked = {
|
|
173
|
+
uploadId: String(i),
|
|
174
|
+
filename: resolved.filename,
|
|
175
|
+
mimetype: resolved.mimetype,
|
|
176
|
+
size: resolved.blob.size,
|
|
177
|
+
source: "local"
|
|
178
|
+
};
|
|
179
|
+
try {
|
|
180
|
+
const uploaded = await this.uploader.upload(resolved.blob, {
|
|
181
|
+
...opts,
|
|
182
|
+
filename: resolved.filename,
|
|
183
|
+
mimeType: resolved.mimetype
|
|
184
|
+
});
|
|
185
|
+
uploaded.uploadId = picked.uploadId;
|
|
186
|
+
filesUploaded.push(uploaded);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
filesFailed.push({ file: picked, error: err });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
const pool = Math.max(1, Math.min(DEFAULT_FILE_CONCURRENCY, sources.length));
|
|
193
|
+
await Promise.all(Array.from({ length: pool }, worker));
|
|
194
|
+
const result = { filesUploaded, filesFailed };
|
|
195
|
+
opts.onUploadDone?.(result);
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
/** Read-only view of the resolved config. */
|
|
199
|
+
get config() {
|
|
200
|
+
return this.cfg;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
function describeUnresolved(source, index, opts) {
|
|
204
|
+
return {
|
|
205
|
+
uploadId: String(index),
|
|
206
|
+
filename: opts.filename || (typeof source === "string" ? basename(source) : "untitled"),
|
|
207
|
+
mimetype: opts.mimeType || "application/octet-stream",
|
|
208
|
+
size: 0,
|
|
209
|
+
source: "local"
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
var UnionStack = {
|
|
213
|
+
/** Create a server-side client. `cfg.apiKey` is required. */
|
|
214
|
+
init(cfg) {
|
|
215
|
+
return new UnionStackNodeClient(cfg);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export { UnionStack, UnionStackNodeClient };
|
|
220
|
+
//# sourceMappingURL=node.js.map
|
|
221
|
+
//# sourceMappingURL=node.js.map
|
package/dist/node.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/node/source.ts","../src/node/index.ts"],"names":["openAsBlob","basename"],"mappings":"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","file":"node.js","sourcesContent":["import { basename, extname } from 'node:path';\nimport { readFile } from 'node:fs/promises';\nimport * as fs from 'node:fs';\nimport { Readable } from 'node:stream';\nimport { makeError } from '../errors.js';\n\n/**\n * Anything the Node client knows how to turn into an upload.\n *\n * • `string` — path to a file on disk (absolute or relative to cwd)\n * • `Buffer` / typed arr — raw bytes already in memory\n * • `ArrayBuffer` — raw bytes\n * • `Blob` / `File` — Web file objects (Node 18+ has `Blob`, 20+ has `File`)\n * • `Readable` — a Node stream (buffered in full before upload, since\n * the multipart protocol needs the total size up front)\n */\nexport type NodeUploadSource =\n | string\n | Buffer\n | Uint8Array\n | ArrayBuffer\n | Blob\n | Readable;\n\nexport interface ResolvedSource {\n /** A Web `Blob` the core Uploader can `.slice()` into multipart chunks. */\n blob: Blob;\n filename: string;\n mimetype: string;\n}\n\n// Smallest map that covers the bulk of real-world server uploads. Anything not\n// listed falls back to application/octet-stream — callers can always override\n// via `{ mimeType }`. Kept deliberately short; this is not a full mime database.\nconst EXT_TO_MIME: Record<string, string> = {\n // images\n png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif',\n webp: 'image/webp', avif: 'image/avif', svg: 'image/svg+xml', bmp: 'image/bmp',\n ico: 'image/x-icon', heic: 'image/heic', tiff: 'image/tiff', tif: 'image/tiff',\n // documents\n pdf: 'application/pdf', txt: 'text/plain', csv: 'text/csv', md: 'text/markdown',\n json: 'application/json', xml: 'application/xml', html: 'text/html', htm: 'text/html',\n // office\n doc: 'application/msword',\n docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n xls: 'application/vnd.ms-excel',\n xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n ppt: 'application/vnd.ms-powerpoint',\n pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n // media\n mp4: 'video/mp4', webm: 'video/webm', mov: 'video/quicktime', mkv: 'video/x-matroska',\n mp3: 'audio/mpeg', wav: 'audio/wav', ogg: 'audio/ogg', m4a: 'audio/mp4',\n // archives\n zip: 'application/zip', gz: 'application/gzip', tar: 'application/x-tar',\n};\n\nfunction mimeFromName(name: string): string {\n const ext = extname(name).slice(1).toLowerCase();\n return EXT_TO_MIME[ext] || 'application/octet-stream';\n}\n\n// `fs.openAsBlob` (Node 19.8+/20+) hands back a Blob backed by the file on disk,\n// so slicing it into parts streams lazily instead of loading the whole file into\n// memory. Typed locally — the global DOM `Blob` is what the Uploader wants, and\n// not every @types/node version exposes openAsBlob.\ntype OpenAsBlob = (path: string, options?: { type?: string }) => Promise<Blob>;\nconst openAsBlob = (fs as unknown as { openAsBlob?: OpenAsBlob }).openAsBlob;\n\n// Node's `Buffer<ArrayBufferLike>` and the DOM lib's `BlobPart`\n// (`ArrayBufferView<ArrayBuffer>`) don't line up under recent @types/node, even\n// though every value passed here is a valid Blob part at runtime. Cast once.\nfunction toBlobPart(data: ArrayBuffer | ArrayBufferView): BlobPart {\n return data as unknown as BlobPart;\n}\n\nfunction isReadable(x: unknown): x is Readable {\n return (\n !!x &&\n typeof (x as Readable).pipe === 'function' &&\n typeof (x as { read?: unknown }).read === 'function'\n );\n}\n\nasync function streamToBuffer(stream: Readable): Promise<Buffer> {\n const chunks: Buffer[] = [];\n for await (const chunk of stream) {\n chunks.push(\n Buffer.isBuffer(chunk)\n ? chunk\n : typeof chunk === 'string'\n ? Buffer.from(chunk)\n : Buffer.from(chunk as Uint8Array),\n );\n }\n return Buffer.concat(chunks);\n}\n\n/**\n * Normalize any {@link NodeUploadSource} into a `{ blob, filename, mimetype }`\n * the core Uploader can consume. Caller-supplied `filename` / `mimeType` always\n * win over what we infer from the path or the blob.\n */\nexport async function resolveSource(\n source: NodeUploadSource,\n opts: { filename?: string; mimeType?: string } = {},\n): Promise<ResolvedSource> {\n // 1. File path on disk.\n if (typeof source === 'string') {\n const filename = opts.filename || basename(source) || 'untitled';\n const mimetype = opts.mimeType || mimeFromName(filename);\n try {\n const blob = openAsBlob\n ? await openAsBlob(source, { type: mimetype })\n : new Blob([toBlobPart(await readFile(source))], { type: mimetype });\n return { blob, filename, mimetype };\n } catch (cause) {\n throw makeError('VALIDATION', `Could not read file at \"${source}\".`, {\n retryable: false,\n cause,\n });\n }\n }\n\n // 2. Already a Blob / File.\n if (typeof Blob !== 'undefined' && source instanceof Blob) {\n const filename = opts.filename || (source as Blob & { name?: string }).name || 'untitled';\n const mimetype = opts.mimeType || source.type || mimeFromName(filename);\n // Only re-wrap when we need to stamp a type the blob doesn't already carry.\n const blob =\n opts.mimeType && opts.mimeType !== source.type\n ? new Blob([source], { type: opts.mimeType })\n : source;\n return { blob, filename, mimetype };\n }\n\n // 3. Node Readable stream — buffer it fully; multipart needs the size up front.\n if (isReadable(source)) {\n const filename = opts.filename || 'untitled';\n const mimetype = opts.mimeType || mimeFromName(filename);\n const buf = await streamToBuffer(source);\n return { blob: new Blob([toBlobPart(buf)], { type: mimetype }), filename, mimetype };\n }\n\n // 4. Buffer / Uint8Array / ArrayBuffer (Buffer is an ArrayBuffer view).\n if (source instanceof ArrayBuffer || ArrayBuffer.isView(source)) {\n const filename = opts.filename || 'untitled';\n const mimetype = opts.mimeType || mimeFromName(filename);\n return { blob: new Blob([toBlobPart(source)], { type: mimetype }), filename, mimetype };\n }\n\n throw makeError(\n 'VALIDATION',\n 'Unsupported upload source. Pass a file path, Buffer, Uint8Array, ArrayBuffer, Blob, or Readable stream.',\n { retryable: false },\n );\n}\n","import { basename } from 'node:path';\nimport { Uploader } from '../uploader.js';\nimport { makeError } from '../errors.js';\nimport { NODE_SDK_VERSION_HEADER } from '../version.js';\nimport { resolveSource, type NodeUploadSource } from './source.js';\nimport type {\n ClientConfig,\n ResolvedClientConfig,\n UploadOptions,\n BatchUploadOptions,\n UploadedFile,\n PickedFile,\n PickResponse,\n UploadError,\n} from '../types.js';\n\n/**\n * UnionStack API URL baked into the SDK. Consumers never see or set this — we\n * ship a new SDK version if the URL ever changes. (Same constant as the browser\n * client; kept independent so the Node entry doesn't pull in client.ts.)\n */\nconst API_BASE = 'https://api.unionstack.link/v1';\n\n// File-level fan-out for uploadMany. Each file still runs its own part-level\n// concurrency (opts.concurrency), so this caps how many files upload at once —\n// keeps a 10k-file batch from opening 10k sockets / file handles simultaneously.\nconst DEFAULT_FILE_CONCURRENCY = 5;\n\n/**\n * Server-side UnionStack client for Node.js. Uploads files straight to your\n * UnionStack bucket from a backend — no browser, no picker, no DOM.\n *\n * Use an API key whose allowed origins include `*` (server-to-server requests\n * carry no `Origin` header). Create the key and add `*` from your dashboard.\n *\n * ```ts\n * import { UnionStack } from '@masters-union/union-stack/node';\n *\n * const client = UnionStack.init({ apiKey: process.env.UNIONSTACK_API_KEY! });\n * const file = await client.upload('./invoice.pdf');\n * console.log(file.url);\n * ```\n */\nexport class UnionStackNodeClient {\n private uploader: Uploader;\n private cfg: ResolvedClientConfig;\n\n constructor(cfg: ClientConfig) {\n // A server SDK can throw on misconfiguration — unlike the browser client,\n // there's no render path to crash.\n if (!cfg?.apiKey) {\n throw makeError('CONFIG', 'apiKey is required. Pass { apiKey: \"unionstack_live_…\" }.', {\n retryable: false,\n });\n }\n this.cfg = {\n ...cfg,\n apiBase: API_BASE,\n // No picker on the server — never prefetch the picker config.\n skipConfigPrefetch: true,\n // Tag the traffic as server-to-server (\"node/…\") so cdn-be can tell it\n // apart from browser SDK calls (\"js/…\").\n sdkHeader: NODE_SDK_VERSION_HEADER,\n };\n this.uploader = new Uploader(this.cfg);\n }\n\n /**\n * Upload one file. Accepts a path on disk, a Buffer/Uint8Array/ArrayBuffer, a\n * Blob/File, or a Readable stream. Returns the stored file (with its public\n * `url`). Rejects with an {@link UploadError}.\n */\n async upload(source: NodeUploadSource, opts: UploadOptions = {}): Promise<UploadedFile> {\n const resolved = await resolveSource(source, { filename: opts.filename, mimeType: opts.mimeType });\n return this.uploader.upload(resolved.blob, {\n ...opts,\n filename: resolved.filename,\n mimeType: resolved.mimetype,\n });\n }\n\n /**\n * Upload many files. Runs up to {@link DEFAULT_FILE_CONCURRENCY} files at once\n * and never rejects on a single file's failure — successes and failures come\n * back separately so a partial failure doesn't drop the whole batch.\n */\n async uploadMany(sources: NodeUploadSource[], opts: BatchUploadOptions = {}): Promise<PickResponse> {\n if (!Array.isArray(sources) || sources.length === 0) {\n const err = makeError('VALIDATION', 'uploadMany requires a non-empty array of sources.', {\n retryable: false,\n });\n opts.onError?.(err);\n throw err;\n }\n\n const filesUploaded: UploadedFile[] = [];\n const filesFailed: Array<{ file: PickedFile; error: UploadError }> = [];\n\n let cursor = 0;\n const worker = async (): Promise<void> => {\n while (true) {\n const i = cursor++;\n if (i >= sources.length) return;\n\n let resolved;\n try {\n resolved = await resolveSource(sources[i], {\n filename: opts.filename,\n mimeType: opts.mimeType,\n });\n } catch (err) {\n filesFailed.push({ file: describeUnresolved(sources[i], i, opts), error: err as UploadError });\n continue;\n }\n\n const picked: PickedFile = {\n uploadId: String(i),\n filename: resolved.filename,\n mimetype: resolved.mimetype,\n size: resolved.blob.size,\n source: 'local',\n };\n try {\n const uploaded = await this.uploader.upload(resolved.blob, {\n ...opts,\n filename: resolved.filename,\n mimeType: resolved.mimetype,\n });\n uploaded.uploadId = picked.uploadId;\n filesUploaded.push(uploaded);\n } catch (err) {\n filesFailed.push({ file: picked, error: err as UploadError });\n }\n }\n };\n\n const pool = Math.max(1, Math.min(DEFAULT_FILE_CONCURRENCY, sources.length));\n await Promise.all(Array.from({ length: pool }, worker));\n\n const result: PickResponse = { filesUploaded, filesFailed };\n opts.onUploadDone?.(result);\n return result;\n }\n\n /** Read-only view of the resolved config. */\n get config(): Readonly<ResolvedClientConfig> {\n return this.cfg;\n }\n}\n\n/** Best-effort descriptor for a file that failed before we could read it. */\nfunction describeUnresolved(\n source: NodeUploadSource,\n index: number,\n opts: BatchUploadOptions,\n): PickedFile {\n return {\n uploadId: String(index),\n filename: opts.filename || (typeof source === 'string' ? basename(source) : 'untitled'),\n mimetype: opts.mimeType || 'application/octet-stream',\n size: 0,\n source: 'local',\n };\n}\n\nexport const UnionStack = {\n /** Create a server-side client. `cfg.apiKey` is required. */\n init(cfg: ClientConfig): UnionStackNodeClient {\n return new UnionStackNodeClient(cfg);\n },\n};\n\nexport type { NodeUploadSource } from './source.js';\nexport type {\n ClientConfig,\n UploadOptions,\n BatchUploadOptions,\n PickedFile,\n UploadedFile,\n ProgressEvent,\n PickResponse,\n UploadError,\n UploadErrorCode,\n Source,\n} from '../types.js';\n"]}
|