@meistrari/vault-sdk 1.4.3 → 1.6.0
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/index.cjs +328 -11
- package/dist/index.d.cts +146 -6
- package/dist/index.d.mts +146 -6
- package/dist/index.d.ts +146 -6
- package/dist/index.mjs +325 -12
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -96,7 +96,7 @@ class Permalink {
|
|
|
96
96
|
/**
|
|
97
97
|
* Get a new permalink instance from its ID.
|
|
98
98
|
*
|
|
99
|
-
* @param
|
|
99
|
+
* @param vaultConfig - The vault config.
|
|
100
100
|
* @param id - The permalink ID.
|
|
101
101
|
* @returns The permalink.
|
|
102
102
|
*/
|
|
@@ -114,7 +114,7 @@ class Permalink {
|
|
|
114
114
|
/**
|
|
115
115
|
* Create a new permalink.
|
|
116
116
|
*
|
|
117
|
-
* @param
|
|
117
|
+
* @param vaultConfig - The vault config.
|
|
118
118
|
* @param params - The parameters for the permalink.
|
|
119
119
|
* @param params.expiresIn - Time, in seconds, the permalink will be valid for.
|
|
120
120
|
* @param params.fileId - The ID of the file to create a permalink for.
|
|
@@ -195,9 +195,92 @@ async function detectFileMimeType(blob) {
|
|
|
195
195
|
if (result?.mime) {
|
|
196
196
|
return result.mime;
|
|
197
197
|
}
|
|
198
|
+
const text = await blob.text();
|
|
199
|
+
const trimmedText = text.trim();
|
|
200
|
+
if (trimmedText.startsWith("{") && trimmedText.endsWith("}") || trimmedText.startsWith("[") && trimmedText.endsWith("]")) {
|
|
201
|
+
try {
|
|
202
|
+
JSON.parse(trimmedText);
|
|
203
|
+
return "application/json";
|
|
204
|
+
} catch {
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const lines = text.split("\n").slice(0, 5).filter((line) => line.trim() !== "");
|
|
208
|
+
if (lines.length > 1) {
|
|
209
|
+
const commaCounts = lines.map((line) => (line.match(/,/g) || []).length);
|
|
210
|
+
const allSame = commaCounts.every((count) => count === commaCounts[0]);
|
|
211
|
+
if (allSame && commaCounts[0] > 0) {
|
|
212
|
+
return "text/csv";
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (!text.includes("\0") && /^[\s\S]{1,1000}$/.test(text.slice(0, 1e3))) {
|
|
216
|
+
return "text/plain";
|
|
217
|
+
}
|
|
198
218
|
return void 0;
|
|
199
219
|
}
|
|
200
220
|
|
|
221
|
+
const name = "@meistrari/vault-sdk";
|
|
222
|
+
const version = "1.6.0";
|
|
223
|
+
const license = "UNLICENSED";
|
|
224
|
+
const repository = {
|
|
225
|
+
type: "git",
|
|
226
|
+
url: "https://github.com/meistrari/vault.git"
|
|
227
|
+
};
|
|
228
|
+
const exports = {
|
|
229
|
+
".": {
|
|
230
|
+
types: "./dist/index.d.ts",
|
|
231
|
+
"import": "./dist/index.mjs",
|
|
232
|
+
require: "./dist/index.cjs"
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
const main = "dist/index.mjs";
|
|
236
|
+
const types = "dist/index.d.ts";
|
|
237
|
+
const files = [
|
|
238
|
+
"dist"
|
|
239
|
+
];
|
|
240
|
+
const scripts = {
|
|
241
|
+
test: "vitest --no-watch",
|
|
242
|
+
"test:watch": "vitest",
|
|
243
|
+
build: "unbuild",
|
|
244
|
+
lint: "eslint .",
|
|
245
|
+
"lint:fix": "eslint . --fix",
|
|
246
|
+
check: "bun run lint && bun tsc --noEmit"
|
|
247
|
+
};
|
|
248
|
+
const dependencies = {
|
|
249
|
+
"@meistrari/vault-shared": "workspace:*",
|
|
250
|
+
"file-type": "21.0.0",
|
|
251
|
+
"mime-types": "3.0.1",
|
|
252
|
+
ofetch: "1.4.1",
|
|
253
|
+
zod: "3.23.8"
|
|
254
|
+
};
|
|
255
|
+
const devDependencies = {
|
|
256
|
+
"@types/bun": "latest",
|
|
257
|
+
"@types/mime-types": "3.0.1",
|
|
258
|
+
msw: "2.6.8",
|
|
259
|
+
unbuild: "2.0.0",
|
|
260
|
+
vitest: "2.1.9"
|
|
261
|
+
};
|
|
262
|
+
const peerDependencies = {
|
|
263
|
+
typescript: "^5.0.0"
|
|
264
|
+
};
|
|
265
|
+
const publishConfig = {
|
|
266
|
+
access: "public"
|
|
267
|
+
};
|
|
268
|
+
const packageJson = {
|
|
269
|
+
name: name,
|
|
270
|
+
version: version,
|
|
271
|
+
license: license,
|
|
272
|
+
repository: repository,
|
|
273
|
+
exports: exports,
|
|
274
|
+
main: main,
|
|
275
|
+
types: types,
|
|
276
|
+
files: files,
|
|
277
|
+
scripts: scripts,
|
|
278
|
+
dependencies: dependencies,
|
|
279
|
+
devDependencies: devDependencies,
|
|
280
|
+
peerDependencies: peerDependencies,
|
|
281
|
+
publishConfig: publishConfig
|
|
282
|
+
};
|
|
283
|
+
|
|
201
284
|
var __defProp = Object.defineProperty;
|
|
202
285
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
203
286
|
var __publicField = (obj, key, value) => {
|
|
@@ -208,8 +291,23 @@ const compatibilityDate = "2025-05-19";
|
|
|
208
291
|
function removeVaultPrefix(url) {
|
|
209
292
|
return url.replace("vault://", "");
|
|
210
293
|
}
|
|
211
|
-
|
|
212
|
-
const
|
|
294
|
+
function detectMimeTypeFromFilename(filename) {
|
|
295
|
+
const extension = filename.split(".").pop()?.toLowerCase();
|
|
296
|
+
if (extension) {
|
|
297
|
+
const mimeType = lookup(`.${extension}`);
|
|
298
|
+
if (mimeType) {
|
|
299
|
+
return mimeType;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return void 0;
|
|
303
|
+
}
|
|
304
|
+
async function wrappedFetch(url, requestInit) {
|
|
305
|
+
const options = {
|
|
306
|
+
...requestInit,
|
|
307
|
+
duplex: requestInit.body instanceof ReadableStream ? "half" : void 0
|
|
308
|
+
};
|
|
309
|
+
const request = new Request(url, options);
|
|
310
|
+
request.headers.set("User-Agent", `vault-js-sdk:${packageJson.version}`);
|
|
213
311
|
const response = await fetch(request);
|
|
214
312
|
if (!response.ok) {
|
|
215
313
|
throw await FetchError.from(request.url, request.method, response);
|
|
@@ -218,9 +316,11 @@ async function wrappedFetch(...params) {
|
|
|
218
316
|
}
|
|
219
317
|
class VaultFile {
|
|
220
318
|
/**
|
|
221
|
-
* Constructs a new VaultFile instance. Direct usage of the constructor is not recommended
|
|
222
|
-
*
|
|
223
|
-
*
|
|
319
|
+
* Constructs a new VaultFile instance. Direct usage of the constructor is not recommended.
|
|
320
|
+
*
|
|
321
|
+
* Use the static methods {@link VaultFile.fromVaultReference} when dealing with an existing file in the vault,
|
|
322
|
+
* {@link VaultFile.fromContent} when preparing a new file for upload,
|
|
323
|
+
* or {@link VaultFile.fromStream} when preparing a new file for streaming upload.
|
|
224
324
|
*
|
|
225
325
|
* @param params - The parameters for the VaultFile constructor
|
|
226
326
|
* @param params.config - The configuration for the VaultFile
|
|
@@ -251,7 +351,9 @@ class VaultFile {
|
|
|
251
351
|
* @returns The headers for the request
|
|
252
352
|
*/
|
|
253
353
|
get headers() {
|
|
254
|
-
|
|
354
|
+
const headers = this.config.authStrategy.getHeaders();
|
|
355
|
+
headers.set("User-Agent", `vault-js-sdk:${packageJson.version}`);
|
|
356
|
+
return headers;
|
|
255
357
|
}
|
|
256
358
|
/**
|
|
257
359
|
* Performs a request to the vault service and handles the response or errors.
|
|
@@ -274,12 +376,16 @@ class VaultFile {
|
|
|
274
376
|
url.searchParams.set(key, value);
|
|
275
377
|
});
|
|
276
378
|
}
|
|
277
|
-
const
|
|
379
|
+
const requestInit = {
|
|
278
380
|
method,
|
|
279
381
|
body,
|
|
280
382
|
headers,
|
|
281
383
|
signal
|
|
282
|
-
}
|
|
384
|
+
};
|
|
385
|
+
if (body && body instanceof ReadableStream) {
|
|
386
|
+
requestInit.duplex = "half";
|
|
387
|
+
}
|
|
388
|
+
const response = await wrappedFetch(url, requestInit);
|
|
283
389
|
if (response.status === 204 || response.headers.get("content-length") === "0") {
|
|
284
390
|
return null;
|
|
285
391
|
}
|
|
@@ -452,6 +558,48 @@ class VaultFile {
|
|
|
452
558
|
}
|
|
453
559
|
return file;
|
|
454
560
|
}
|
|
561
|
+
/**
|
|
562
|
+
* Creates a new VaultFile instance for streaming upload workflows.
|
|
563
|
+
* This method creates a VaultFile with placeholder content that's optimized for streaming uploads.
|
|
564
|
+
*
|
|
565
|
+
* @param params - The parameters for creating a VaultFile for streaming
|
|
566
|
+
* @param params.name - The name of the file
|
|
567
|
+
* @param params.contentLength - The size of the content in bytes
|
|
568
|
+
* @param params.config - The configuration for the VaultFile
|
|
569
|
+
* @param params.contentType - The MIME type of the content (optional)
|
|
570
|
+
* @param options - The options for the request
|
|
571
|
+
* @param options.signal - The signal to abort the request
|
|
572
|
+
*
|
|
573
|
+
* @returns A new VaultFile instance ready for streaming upload
|
|
574
|
+
*
|
|
575
|
+
* @example
|
|
576
|
+
* ```ts
|
|
577
|
+
* // Create VaultFile for streaming
|
|
578
|
+
* const vaultFile = await VaultFile.fromStream({
|
|
579
|
+
* name: 'large-video.mp4',
|
|
580
|
+
* contentLength: 100 * 1024 * 1024, // 100MB
|
|
581
|
+
* contentType: 'video/mp4',
|
|
582
|
+
* config: { vaultUrl, authStrategy }
|
|
583
|
+
* })
|
|
584
|
+
*
|
|
585
|
+
* // Upload using a stream
|
|
586
|
+
* const fileStream = file.stream()
|
|
587
|
+
* await vaultFile.uploadStream(fileStream, {
|
|
588
|
+
* contentLength: file.size,
|
|
589
|
+
* contentType: file.type
|
|
590
|
+
* })
|
|
591
|
+
* ```
|
|
592
|
+
*/
|
|
593
|
+
static async fromStream(params, options) {
|
|
594
|
+
const { name, contentLength, config: vaultConfig, contentType } = params;
|
|
595
|
+
const config = resolveConfig(vaultConfig);
|
|
596
|
+
const file = new VaultFile({ config, name });
|
|
597
|
+
await file._createFile({
|
|
598
|
+
size: contentLength,
|
|
599
|
+
mimeType: contentType || "application/octet-stream"
|
|
600
|
+
}, { signal: options?.signal });
|
|
601
|
+
return file;
|
|
602
|
+
}
|
|
455
603
|
/**
|
|
456
604
|
* Populates the metadata of the file instance.
|
|
457
605
|
* @param options - The options for the request
|
|
@@ -619,6 +767,90 @@ class VaultFile {
|
|
|
619
767
|
return blob;
|
|
620
768
|
return await blobToBase64(blob);
|
|
621
769
|
}
|
|
770
|
+
/**
|
|
771
|
+
* Downloads a file from the vault as a stream for memory-efficient processing.
|
|
772
|
+
*
|
|
773
|
+
* @param options - The options for the request
|
|
774
|
+
* @param options.signal - The signal to abort the request
|
|
775
|
+
*
|
|
776
|
+
* @returns A ReadableStream that yields chunks of the file data
|
|
777
|
+
*
|
|
778
|
+
* @example
|
|
779
|
+
* ```ts
|
|
780
|
+
* const vaultFile = await VaultFile.fromVaultReference('vault://1234567890', { vaultUrl, authStrategy })
|
|
781
|
+
* const stream = await vaultFile.downloadStream()
|
|
782
|
+
*
|
|
783
|
+
* // Process the stream chunk by chunk
|
|
784
|
+
* const reader = stream.getReader()
|
|
785
|
+
* try {
|
|
786
|
+
* while (true) {
|
|
787
|
+
* const { done, value } = await reader.read()
|
|
788
|
+
* if (done) break
|
|
789
|
+
* // Process the chunk (Uint8Array)
|
|
790
|
+
* console.log('Received chunk of size:', value.length)
|
|
791
|
+
* }
|
|
792
|
+
* } finally {
|
|
793
|
+
* reader.releaseLock()
|
|
794
|
+
* }
|
|
795
|
+
* ```
|
|
796
|
+
*/
|
|
797
|
+
async downloadStream(options) {
|
|
798
|
+
const downloadUrl = await this.getDownloadUrl({ signal: options?.signal });
|
|
799
|
+
const response = await wrappedFetch(downloadUrl, {
|
|
800
|
+
method: "GET",
|
|
801
|
+
signal: options?.signal
|
|
802
|
+
});
|
|
803
|
+
if (!response.body) {
|
|
804
|
+
throw new Error("Response body is not readable");
|
|
805
|
+
}
|
|
806
|
+
return response.body;
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Uploads a file to the vault using a stream for memory-efficient processing.
|
|
810
|
+
*
|
|
811
|
+
* @param stream - The readable stream of file data to upload
|
|
812
|
+
* @param options - The options for the request
|
|
813
|
+
* @param options.signal - The signal to abort the request
|
|
814
|
+
* @param options.contentLength - The total size of the content (required for S3 uploads)
|
|
815
|
+
* @param options.contentType - The MIME type of the content (will be detected if not provided)
|
|
816
|
+
*
|
|
817
|
+
* @throws {Error} If contentLength is not provided
|
|
818
|
+
* @throws {FetchError} If the upload fails
|
|
819
|
+
* @returns Promise that resolves when upload is complete
|
|
820
|
+
*
|
|
821
|
+
* @example
|
|
822
|
+
* ```ts
|
|
823
|
+
* const file = new File(['content'], 'document.txt')
|
|
824
|
+
* const vaultFile = await VaultFile.fromStream('document.txt', file.size, {
|
|
825
|
+
* contentType: file.type,
|
|
826
|
+
* config: { vaultUrl, authStrategy }
|
|
827
|
+
* })
|
|
828
|
+
*
|
|
829
|
+
* // Upload using the stream directly
|
|
830
|
+
* const stream = file.stream()
|
|
831
|
+
* await vaultFile.uploadStream(stream, {
|
|
832
|
+
* contentLength: file.size,
|
|
833
|
+
* contentType: file.type
|
|
834
|
+
* })
|
|
835
|
+
* ```
|
|
836
|
+
*/
|
|
837
|
+
async uploadStream(stream, options) {
|
|
838
|
+
const { contentLength, contentType, signal } = options;
|
|
839
|
+
if (contentLength === void 0 || contentLength < 0) {
|
|
840
|
+
throw new Error("contentLength must be provided and non-negative for streaming uploads");
|
|
841
|
+
}
|
|
842
|
+
const uploadUrl = await this.getUploadUrl({ signal });
|
|
843
|
+
const mimeType = contentType ?? this.metadata?.mimeType ?? (this.name ? detectMimeTypeFromFilename(this.name) : void 0) ?? "application/octet-stream";
|
|
844
|
+
const headers = new Headers();
|
|
845
|
+
headers.set("Content-Type", mimeType);
|
|
846
|
+
headers.set("Content-Length", contentLength.toString());
|
|
847
|
+
await wrappedFetch(uploadUrl, {
|
|
848
|
+
method: "PUT",
|
|
849
|
+
body: stream,
|
|
850
|
+
headers,
|
|
851
|
+
signal
|
|
852
|
+
});
|
|
853
|
+
}
|
|
622
854
|
/**
|
|
623
855
|
* Deletes the file from the vault.
|
|
624
856
|
* @param options - The options for the request
|
|
@@ -677,6 +909,79 @@ class VaultFile {
|
|
|
677
909
|
}
|
|
678
910
|
}
|
|
679
911
|
|
|
912
|
+
function isS3UrlExpired(url) {
|
|
913
|
+
try {
|
|
914
|
+
const urlObj = new URL(url);
|
|
915
|
+
const amzDate = urlObj.searchParams.get("X-Amz-Date");
|
|
916
|
+
const amzExpires = urlObj.searchParams.get("X-Amz-Expires");
|
|
917
|
+
if (!amzDate || !amzExpires)
|
|
918
|
+
return false;
|
|
919
|
+
const year = Number.parseInt(amzDate.substring(0, 4));
|
|
920
|
+
const month = Number.parseInt(amzDate.substring(4, 6)) - 1;
|
|
921
|
+
const day = Number.parseInt(amzDate.substring(6, 8));
|
|
922
|
+
const hours = Number.parseInt(amzDate.substring(9, 11));
|
|
923
|
+
const minutes = Number.parseInt(amzDate.substring(11, 13));
|
|
924
|
+
const seconds = Number.parseInt(amzDate.substring(13, 15));
|
|
925
|
+
const signedDate = new Date(Date.UTC(year, month, day, hours, minutes, seconds));
|
|
926
|
+
const expiresInSeconds = Number.parseInt(amzExpires);
|
|
927
|
+
const expirationTime = signedDate.getTime() + expiresInSeconds * 1e3;
|
|
928
|
+
const currentTime = Date.now();
|
|
929
|
+
return currentTime > expirationTime;
|
|
930
|
+
} catch {
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
function isVaultReference(url) {
|
|
935
|
+
return url.startsWith("vault://") && url.length === 10;
|
|
936
|
+
}
|
|
937
|
+
function isVaultFileS3Url(url) {
|
|
938
|
+
const urlObj = new URL(url);
|
|
939
|
+
const amzDate = urlObj.searchParams.get("X-Amz-Date");
|
|
940
|
+
const amzExpires = urlObj.searchParams.get("X-Amz-Expires");
|
|
941
|
+
return urlObj.hostname.includes("amazonaws.com") && Boolean(amzDate) && Boolean(amzExpires);
|
|
942
|
+
}
|
|
943
|
+
const URL_STRATEGIES = [
|
|
944
|
+
{
|
|
945
|
+
separator: "/",
|
|
946
|
+
extractSegments: ([workspaceId, vaultFileId]) => ({
|
|
947
|
+
workspaceId,
|
|
948
|
+
vaultFileId
|
|
949
|
+
})
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
separator: "_",
|
|
953
|
+
extractSegments: ([vaultFileId, workspaceId]) => ({
|
|
954
|
+
vaultFileId,
|
|
955
|
+
workspaceId
|
|
956
|
+
})
|
|
957
|
+
}
|
|
958
|
+
];
|
|
959
|
+
function extractVaultFileIdFromS3Url(url) {
|
|
960
|
+
if (isVaultReference(url))
|
|
961
|
+
return url.replace("vault://", "");
|
|
962
|
+
try {
|
|
963
|
+
if (!isVaultFileS3Url(url))
|
|
964
|
+
return null;
|
|
965
|
+
const urlObj = new URL(url);
|
|
966
|
+
const strategy = URL_STRATEGIES.find((strategy2) => urlObj.pathname.includes(strategy2.separator));
|
|
967
|
+
if (!strategy)
|
|
968
|
+
return null;
|
|
969
|
+
const segments = urlObj.pathname.split(strategy.separator).filter((segment) => segment.length > 0);
|
|
970
|
+
if (segments.length < 2)
|
|
971
|
+
return null;
|
|
972
|
+
const extractedIds = strategy.extractSegments(segments);
|
|
973
|
+
if (!extractedIds || !extractedIds.vaultFileId || !extractedIds.workspaceId || extractedIds.vaultFileId.length < 32)
|
|
974
|
+
return null;
|
|
975
|
+
return extractedIds.vaultFileId;
|
|
976
|
+
} catch {
|
|
977
|
+
return null;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
function convertS3UrlToVaultReference(url) {
|
|
981
|
+
const vaultFileId = extractVaultFileIdFromS3Url(url);
|
|
982
|
+
return vaultFileId ? `vault://${vaultFileId}` : null;
|
|
983
|
+
}
|
|
984
|
+
|
|
680
985
|
function vaultClient(vaultConfig) {
|
|
681
986
|
const config = resolveConfig(vaultConfig);
|
|
682
987
|
function createFromContent(name, content, options) {
|
|
@@ -692,7 +997,15 @@ function vaultClient(vaultConfig) {
|
|
|
692
997
|
config
|
|
693
998
|
}, { signal: options?.signal });
|
|
694
999
|
}
|
|
695
|
-
|
|
1000
|
+
async function createFromStream(name, contentLength, options) {
|
|
1001
|
+
return VaultFile.fromStream({
|
|
1002
|
+
name,
|
|
1003
|
+
contentLength,
|
|
1004
|
+
config,
|
|
1005
|
+
contentType: options?.contentType
|
|
1006
|
+
}, { signal: options?.signal });
|
|
1007
|
+
}
|
|
1008
|
+
return { createFromContent, createFromReference, createFromStream };
|
|
696
1009
|
}
|
|
697
1010
|
|
|
698
|
-
export { APIKeyAuthStrategy, DataTokenAuthStrategy, FetchError, VaultFile, vaultClient };
|
|
1011
|
+
export { APIKeyAuthStrategy, DataTokenAuthStrategy, FetchError, VaultFile, convertS3UrlToVaultReference, extractVaultFileIdFromS3Url, isS3UrlExpired, isVaultFileS3Url, vaultClient };
|