@masters-union/union-stack 0.3.8 → 0.3.9

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/node.cjs ADDED
@@ -0,0 +1,244 @@
1
+ 'use strict';
2
+
3
+ var chunkZOOUTUWU_cjs = require('./chunk-ZOOUTUWU.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 chunkZOOUTUWU_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 chunkZOOUTUWU_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 chunkZOOUTUWU_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: chunkZOOUTUWU_cjs.NODE_SDK_VERSION_HEADER
148
+ };
149
+ this.uploader = new chunkZOOUTUWU_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 = chunkZOOUTUWU_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":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,IAAM,WAAA,GAAsC;AAAA;AAAA,EAE1C,GAAA,EAAK,WAAA;AAAA,EAAa,GAAA,EAAK,YAAA;AAAA,EAAc,IAAA,EAAM,YAAA;AAAA,EAAc,GAAA,EAAK,WAAA;AAAA,EAC9D,IAAA,EAAM,YAAA;AAAA,EAAc,IAAA,EAAM,YAAA;AAAA,EAAc,GAAA,EAAK,eAAA;AAAA,EAAiB,GAAA,EAAK,WAAA;AAAA,EACnE,GAAA,EAAK,cAAA;AAAA,EAAgB,IAAA,EAAM,YAAA;AAAA,EAAc,IAAA,EAAM,YAAA;AAAA,EAAc,GAAA,EAAK,YAAA;AAAA;AAAA,EAElE,GAAA,EAAK,iBAAA;AAAA,EAAmB,GAAA,EAAK,YAAA;AAAA,EAAc,GAAA,EAAK,UAAA;AAAA,EAAY,EAAA,EAAI,eAAA;AAAA,EAChE,IAAA,EAAM,kBAAA;AAAA,EAAoB,GAAA,EAAK,iBAAA;AAAA,EAAmB,IAAA,EAAM,WAAA;AAAA,EAAa,GAAA,EAAK,WAAA;AAAA;AAAA,EAE1E,GAAA,EAAK,oBAAA;AAAA,EACL,IAAA,EAAM,yEAAA;AAAA,EACN,GAAA,EAAK,0BAAA;AAAA,EACL,IAAA,EAAM,mEAAA;AAAA,EACN,GAAA,EAAK,+BAAA;AAAA,EACL,IAAA,EAAM,2EAAA;AAAA;AAAA,EAEN,GAAA,EAAK,WAAA;AAAA,EAAa,IAAA,EAAM,YAAA;AAAA,EAAc,GAAA,EAAK,iBAAA;AAAA,EAAmB,GAAA,EAAK,kBAAA;AAAA,EACnE,GAAA,EAAK,YAAA;AAAA,EAAc,GAAA,EAAK,WAAA;AAAA,EAAa,GAAA,EAAK,WAAA;AAAA,EAAa,GAAA,EAAK,WAAA;AAAA;AAAA,EAE5D,GAAA,EAAK,iBAAA;AAAA,EAAmB,EAAA,EAAI,kBAAA;AAAA,EAAoB,GAAA,EAAK;AACvD,CAAA;AAEA,SAAS,aAAa,IAAA,EAAsB;AAC1C,EAAA,MAAM,MAAMA,YAAA,CAAQ,IAAI,EAAE,KAAA,CAAM,CAAC,EAAE,WAAA,EAAY;AAC/C,EAAA,OAAO,WAAA,CAAY,GAAG,CAAA,IAAK,0BAAA;AAC7B;AAOA,IAAMC,WAAAA,GAA4DC,aAAA,CAAA,UAAA;AAKlE,SAAS,WAAW,IAAA,EAA+C;AACjE,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,WAAW,CAAA,EAA2B;AAC7C,EAAA,OACE,CAAC,CAAC,CAAA,IACF,OAAQ,EAAe,IAAA,KAAS,UAAA,IAChC,OAAQ,CAAA,CAAyB,IAAA,KAAS,UAAA;AAE9C;AAEA,eAAe,eAAe,MAAA,EAAmC;AAC/D,EAAA,MAAM,SAAmB,EAAC;AAC1B,EAAA,WAAA,MAAiB,SAAS,MAAA,EAAQ;AAChC,IAAA,MAAA,CAAO,IAAA;AAAA,MACL,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,GACjB,QACA,OAAO,KAAA,KAAU,QAAA,GACf,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,GACjB,MAAA,CAAO,KAAK,KAAmB;AAAA,KACvC;AAAA,EACF;AACA,EAAA,OAAO,MAAA,CAAO,OAAO,MAAM,CAAA;AAC7B;AAOA,eAAsB,aAAA,CACpB,MAAA,EACA,IAAA,GAAiD,EAAC,EACzB;AAEzB,EAAA,IAAI,OAAO,WAAW,QAAA,EAAU;AAC9B,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,QAAA,IAAYC,aAAA,CAAS,MAAM,CAAA,IAAK,UAAA;AACtD,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,QAAA,IAAY,YAAA,CAAa,QAAQ,CAAA;AACvD,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAOF,cACT,MAAMA,WAAAA,CAAW,QAAQ,EAAE,IAAA,EAAM,QAAA,EAAU,CAAA,GAC3C,IAAI,KAAK,CAAC,UAAA,CAAW,MAAMG,iBAAA,CAAS,MAAM,CAAC,CAAC,CAAA,EAAG,EAAE,IAAA,EAAM,QAAA,EAAU,CAAA;AACrE,MAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,QAAA,EAAS;AAAA,IACpC,SAAS,KAAA,EAAO;AACd,MAAA,MAAMC,2BAAA,CAAU,YAAA,EAAc,CAAA,wBAAA,EAA2B,MAAM,CAAA,EAAA,CAAA,EAAM;AAAA,QACnE,SAAA,EAAW,KAAA;AAAA,QACX;AAAA,OACD,CAAA;AAAA,IACH;AAAA,EACF;AAGA,EAAA,IAAI,OAAO,IAAA,KAAS,WAAA,IAAe,MAAA,YAAkB,IAAA,EAAM;AACzD,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,QAAA,IAAa,MAAA,CAAoC,IAAA,IAAQ,UAAA;AAC/E,IAAA,MAAM,WAAW,IAAA,CAAK,QAAA,IAAY,MAAA,CAAO,IAAA,IAAQ,aAAa,QAAQ,CAAA;AAEtE,IAAA,MAAM,OACJ,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,QAAA,KAAa,OAAO,IAAA,GACtC,IAAI,IAAA,CAAK,CAAC,MAAM,CAAA,EAAG,EAAE,MAAM,IAAA,CAAK,QAAA,EAAU,CAAA,GAC1C,MAAA;AACN,IAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,QAAA,EAAS;AAAA,EACpC;AAGA,EAAA,IAAI,UAAA,CAAW,MAAM,CAAA,EAAG;AACtB,IAAA,MAAM,QAAA,GAAW,KAAK,QAAA,IAAY,UAAA;AAClC,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,QAAA,IAAY,YAAA,CAAa,QAAQ,CAAA;AACvD,IAAA,MAAM,GAAA,GAAM,MAAM,cAAA,CAAe,MAAM,CAAA;AACvC,IAAA,OAAO,EAAE,IAAA,EAAM,IAAI,IAAA,CAAK,CAAC,UAAA,CAAW,GAAG,CAAC,CAAA,EAAG,EAAE,IAAA,EAAM,QAAA,EAAU,CAAA,EAAG,UAAU,QAAA,EAAS;AAAA,EACrF;AAGA,EAAA,IAAI,MAAA,YAAkB,WAAA,IAAe,WAAA,CAAY,MAAA,CAAO,MAAM,CAAA,EAAG;AAC/D,IAAA,MAAM,QAAA,GAAW,KAAK,QAAA,IAAY,UAAA;AAClC,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,QAAA,IAAY,YAAA,CAAa,QAAQ,CAAA;AACvD,IAAA,OAAO,EAAE,IAAA,EAAM,IAAI,IAAA,CAAK,CAAC,UAAA,CAAW,MAAM,CAAC,CAAA,EAAG,EAAE,IAAA,EAAM,QAAA,EAAU,CAAA,EAAG,UAAU,QAAA,EAAS;AAAA,EACxF;AAEA,EAAA,MAAMA,2BAAA;AAAA,IACJ,YAAA;AAAA,IACA,yGAAA;AAAA,IACA,EAAE,WAAW,KAAA;AAAM,GACrB;AACF;;;ACtIA,IAAM,QAAA,GAAW,gCAAA;AAKjB,IAAM,wBAAA,GAA2B,CAAA;AAiB1B,IAAM,uBAAN,MAA2B;AAAA,EAIhC,YAAY,GAAA,EAAmB;AAG7B,IAAA,IAAI,CAAC,KAAK,MAAA,EAAQ;AAChB,MAAA,MAAMA,2BAAA,CAAU,UAAU,gEAAA,EAA6D;AAAA,QACrF,SAAA,EAAW;AAAA,OACZ,CAAA;AAAA,IACH;AACA,IAAA,IAAA,CAAK,GAAA,GAAM;AAAA,MACT,GAAG,GAAA;AAAA,MACH,OAAA,EAAS,QAAA;AAAA;AAAA,MAET,kBAAA,EAAoB,IAAA;AAAA;AAAA;AAAA,MAGpB,SAAA,EAAWC;AAAA,KACb;AACA,IAAA,IAAA,CAAK,QAAA,GAAW,IAAIC,0BAAA,CAAS,IAAA,CAAK,GAAG,CAAA;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,MAAA,CAAO,MAAA,EAA0B,IAAA,GAAsB,EAAC,EAA0B;AACtF,IAAA,MAAM,QAAA,GAAW,MAAM,aAAA,CAAc,MAAA,EAAQ,EAAE,QAAA,EAAU,IAAA,CAAK,QAAA,EAAU,QAAA,EAAU,IAAA,CAAK,QAAA,EAAU,CAAA;AACjG,IAAA,OAAO,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO,QAAA,CAAS,IAAA,EAAM;AAAA,MACzC,GAAG,IAAA;AAAA,MACH,UAAU,QAAA,CAAS,QAAA;AAAA,MACnB,UAAU,QAAA,CAAS;AAAA,KACpB,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,UAAA,CAAW,OAAA,EAA6B,IAAA,GAA2B,EAAC,EAA0B;AAClG,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,IAAK,OAAA,CAAQ,WAAW,CAAA,EAAG;AACnD,MAAA,MAAM,GAAA,GAAMF,2BAAA,CAAU,YAAA,EAAc,mDAAA,EAAqD;AAAA,QACvF,SAAA,EAAW;AAAA,OACZ,CAAA;AACD,MAAA,IAAA,CAAK,UAAU,GAAG,CAAA;AAClB,MAAA,MAAM,GAAA;AAAA,IACR;AAEA,IAAA,MAAM,gBAAgC,EAAC;AACvC,IAAA,MAAM,cAA+D,EAAC;AAEtE,IAAA,IAAI,MAAA,GAAS,CAAA;AACb,IAAA,MAAM,SAAS,YAA2B;AACxC,MAAA,OAAO,IAAA,EAAM;AACX,QAAA,MAAM,CAAA,GAAI,MAAA,EAAA;AACV,QAAA,IAAI,CAAA,IAAK,QAAQ,MAAA,EAAQ;AAEzB,QAAA,IAAI,QAAA;AACJ,QAAA,IAAI;AACF,UAAA,QAAA,GAAW,MAAM,aAAA,CAAc,OAAA,CAAQ,CAAC,CAAA,EAAG;AAAA,YACzC,UAAU,IAAA,CAAK,QAAA;AAAA,YACf,UAAU,IAAA,CAAK;AAAA,WAChB,CAAA;AAAA,QACH,SAAS,GAAA,EAAK;AACZ,UAAA,WAAA,CAAY,IAAA,CAAK,EAAE,IAAA,EAAM,kBAAA,CAAmB,OAAA,CAAQ,CAAC,CAAA,EAAG,CAAA,EAAG,IAAI,CAAA,EAAG,KAAA,EAAO,GAAA,EAAoB,CAAA;AAC7F,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,MAAA,GAAqB;AAAA,UACzB,QAAA,EAAU,OAAO,CAAC,CAAA;AAAA,UAClB,UAAU,QAAA,CAAS,QAAA;AAAA,UACnB,UAAU,QAAA,CAAS,QAAA;AAAA,UACnB,IAAA,EAAM,SAAS,IAAA,CAAK,IAAA;AAAA,UACpB,MAAA,EAAQ;AAAA,SACV;AACA,QAAA,IAAI;AACF,UAAA,MAAM,WAAW,MAAM,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO,SAAS,IAAA,EAAM;AAAA,YACzD,GAAG,IAAA;AAAA,YACH,UAAU,QAAA,CAAS,QAAA;AAAA,YACnB,UAAU,QAAA,CAAS;AAAA,WACpB,CAAA;AACD,UAAA,QAAA,CAAS,WAAW,MAAA,CAAO,QAAA;AAC3B,UAAA,aAAA,CAAc,KAAK,QAAQ,CAAA;AAAA,QAC7B,SAAS,GAAA,EAAK;AACZ,UAAA,WAAA,CAAY,KAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,KAAA,EAAO,KAAoB,CAAA;AAAA,QAC9D;AAAA,MACF;AAAA,IACF,CAAA;AAEA,IAAA,MAAM,IAAA,GAAO,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,wBAAA,EAA0B,OAAA,CAAQ,MAAM,CAAC,CAAA;AAC3E,IAAA,MAAM,OAAA,CAAQ,IAAI,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,IAAA,EAAK,EAAG,MAAM,CAAC,CAAA;AAEtD,IAAA,MAAM,MAAA,GAAuB,EAAE,aAAA,EAAe,WAAA,EAAY;AAC1D,IAAA,IAAA,CAAK,eAAe,MAAM,CAAA;AAC1B,IAAA,OAAO,MAAA;AAAA,EACT;AAAA;AAAA,EAGA,IAAI,MAAA,GAAyC;AAC3C,IAAA,OAAO,IAAA,CAAK,GAAA;AAAA,EACd;AACF;AAGA,SAAS,kBAAA,CACP,MAAA,EACA,KAAA,EACA,IAAA,EACY;AACZ,EAAA,OAAO;AAAA,IACL,QAAA,EAAU,OAAO,KAAK,CAAA;AAAA,IACtB,QAAA,EAAU,KAAK,QAAA,KAAa,OAAO,WAAW,QAAA,GAAWF,aAAAA,CAAS,MAAM,CAAA,GAAI,UAAA,CAAA;AAAA,IAC5E,QAAA,EAAU,KAAK,QAAA,IAAY,0BAAA;AAAA,IAC3B,IAAA,EAAM,CAAA;AAAA,IACN,MAAA,EAAQ;AAAA,GACV;AACF;AAEO,IAAM,UAAA,GAAa;AAAA;AAAA,EAExB,KAAK,GAAA,EAAyC;AAC5C,IAAA,OAAO,IAAI,qBAAqB,GAAG,CAAA;AAAA,EACrC;AACF","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"]}
@@ -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-50o0pbff.cjs';
3
+ export { a as PickedFile, b as ProgressEvent, S as Source, U as UploadError, c as UploadErrorCode } from './types-50o0pbff.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-50o0pbff.js';
3
+ export { a as PickedFile, b as ProgressEvent, S as Source, U as UploadError, c as UploadErrorCode } from './types-50o0pbff.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-YNWENXR2.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
@@ -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"]}