@lumeweb/pinner 0.1.13 → 0.1.15
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/esm/adapters/pinata/index.d.ts +1 -1
- package/dist/esm/adapters/pinata/shared/index.d.ts +1 -1
- package/dist/esm/adapters/pinata/shared/types.d.ts +41 -1
- package/dist/esm/adapters/pinata/v2/adapter-interface.d.ts +4 -1
- package/dist/esm/adapters/pinata/v2/index.d.ts +1 -1
- package/dist/esm/adapters/pinata/v2/types.d.ts +58 -1
- package/dist/esm/api/client.d.ts +21 -0
- package/dist/esm/api/client.js +54 -0
- package/dist/esm/api/client.js.map +1 -0
- package/dist/esm/api/generated/content/content.msw.js +30 -37
- package/dist/esm/api/generated/content/content.msw.js.map +1 -1
- package/dist/esm/api/generated/dns/dns.msw.js +30 -44
- package/dist/esm/api/generated/dns/dns.msw.js.map +1 -1
- package/dist/esm/api/generated/files/files.msw.js +30 -33
- package/dist/esm/api/generated/files/files.msw.js.map +1 -1
- package/dist/esm/api/generated/gateway/gateway.msw.js +30 -32
- package/dist/esm/api/generated/gateway/gateway.msw.js.map +1 -1
- package/dist/esm/api/generated/internal/internal.msw.js +30 -31
- package/dist/esm/api/generated/internal/internal.msw.js.map +1 -1
- package/dist/esm/api/generated/ipns/ipns.msw.js +30 -37
- package/dist/esm/api/generated/ipns/ipns.msw.js.map +1 -1
- package/dist/esm/api/generated/pinning/pinning.msw.js +30 -35
- package/dist/esm/api/generated/pinning/pinning.msw.js.map +1 -1
- package/dist/esm/api/generated/schemas/blockMetaResponse.d.ts +29 -29
- package/dist/esm/api/generated/schemas/component.d.ts +29 -29
- package/dist/esm/api/generated/schemas/errorResponse.d.ts +29 -29
- package/dist/esm/api/generated/schemas/fileManagerItem.d.ts +29 -29
- package/dist/esm/api/generated/schemas/gatewayWebsiteResponse.d.ts +29 -29
- package/dist/esm/api/generated/schemas/gatewayWebsiteStatusResponse.d.ts +29 -29
- package/dist/esm/api/generated/schemas/getBlockMetaBatchRequest.d.ts +42 -0
- package/dist/esm/api/generated/schemas/iPNSKeyListResponse.d.ts +29 -29
- package/dist/esm/api/generated/schemas/iPNSKeyRequest.d.ts +29 -29
- package/dist/esm/api/generated/schemas/iPNSKeyResponse.d.ts +29 -29
- package/dist/esm/api/generated/schemas/iPNSPublishRequest.d.ts +29 -29
- package/dist/esm/api/generated/schemas/iPNSPublishResponse.d.ts +29 -29
- package/dist/esm/api/generated/schemas/iPNSRepublishResponse.d.ts +29 -29
- package/dist/esm/api/generated/schemas/iPNSResolveResponse.d.ts +29 -29
- package/dist/esm/api/generated/schemas/infoResponse.d.ts +29 -29
- package/dist/esm/api/generated/schemas/pinRequestMeta.d.ts +29 -29
- package/dist/esm/api/generated/schemas/pinStatusResponseInfo.d.ts +29 -29
- package/dist/esm/api/generated/schemas/postApiUploadBody.d.ts +42 -0
- package/dist/esm/api/generated/schemas/postUploadResponse.d.ts +29 -29
- package/dist/esm/api/generated/schemas/recordResponse.d.ts +29 -29
- package/dist/esm/api/generated/schemas/recordResult.d.ts +29 -29
- package/dist/esm/api/generated/schemas/sSLStatusInfo.d.ts +29 -29
- package/dist/esm/api/generated/schemas/sSLStatusUpdateRequest.d.ts +44 -0
- package/dist/esm/api/generated/schemas/uploadResultResponse.d.ts +29 -29
- package/dist/esm/api/generated/schemas/validationResponse.d.ts +29 -29
- package/dist/esm/api/generated/schemas/websiteConfigResponse.d.ts +29 -29
- package/dist/esm/api/generated/schemas/websiteRequest.d.ts +29 -29
- package/dist/esm/api/generated/schemas/websiteUpdateRequest.d.ts +29 -29
- package/dist/esm/api/generated/schemas/websiteValidateResponse.d.ts +29 -29
- package/dist/esm/api/generated/schemas/zoneListResponse.d.ts +29 -29
- package/dist/esm/api/generated/schemas/zoneResponse.d.ts +29 -29
- package/dist/esm/api/generated/tus/tus.msw.js +30 -34
- package/dist/esm/api/generated/tus/tus.msw.js.map +1 -1
- package/dist/esm/api/generated/websites/websites.msw.js +30 -38
- package/dist/esm/api/generated/websites/websites.msw.js.map +1 -1
- package/dist/esm/api/ipns.d.ts +47 -6
- package/dist/esm/api/ipns.js +46 -39
- package/dist/esm/api/ipns.js.map +1 -1
- package/dist/esm/api/websites.d.ts +57 -6
- package/dist/esm/api/websites.js +57 -41
- package/dist/esm/api/websites.js.map +1 -1
- package/dist/esm/auth/index.js +2 -0
- package/dist/esm/auth/key-exchange.js +76 -0
- package/dist/esm/auth/key-exchange.js.map +1 -0
- package/dist/esm/auth/manager.d.ts +33 -0
- package/dist/esm/auth/manager.js +29 -0
- package/dist/esm/auth/manager.js.map +1 -0
- package/dist/esm/blockstore/index.d.ts +1 -1
- package/dist/esm/blockstore/unstorage-base.d.ts +6 -1
- package/dist/esm/blockstore/unstorage-base.js +9 -6
- package/dist/esm/blockstore/unstorage-base.js.map +1 -1
- package/dist/esm/config.js +13 -0
- package/dist/esm/config.js.map +1 -0
- package/dist/esm/index.d.ts +31 -6
- package/dist/esm/pin/client.js +14 -14
- package/dist/esm/pin/client.js.map +1 -1
- package/dist/esm/pinner.d.ts +22 -0
- package/dist/esm/pinner.js +31 -4
- package/dist/esm/pinner.js.map +1 -1
- package/dist/esm/types/pin.d.ts +42 -0
- package/dist/esm/upload/base-upload.js +6 -4
- package/dist/esm/upload/base-upload.js.map +1 -1
- package/dist/esm/upload/builder.d.ts +1 -1
- package/dist/esm/upload/car.d.ts +18 -0
- package/dist/esm/upload/car.js.map +1 -1
- package/dist/esm/upload/manager.d.ts +33 -1
- package/dist/esm/upload/manager.js +50 -4
- package/dist/esm/upload/manager.js.map +1 -1
- package/dist/esm/upload/normalize.js.map +1 -1
- package/dist/esm/upload/tus-upload.js +4 -4
- package/dist/esm/upload/tus-upload.js.map +1 -1
- package/dist/esm/upload/xhr-upload.js +2 -2
- package/dist/esm/upload/xhr-upload.js.map +1 -1
- package/package.json +18 -17
package/dist/esm/pin/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NotFoundError } from "../errors/index.js";
|
|
2
2
|
import { CID } from "multiformats/cid";
|
|
3
3
|
import { Configuration, RemotePinningServiceClient } from "@ipfs-shipyard/pinning-service-client";
|
|
4
4
|
|
|
@@ -6,22 +6,22 @@ import { Configuration, RemotePinningServiceClient } from "@ipfs-shipyard/pinnin
|
|
|
6
6
|
var PinClient = class {
|
|
7
7
|
client = null;
|
|
8
8
|
config;
|
|
9
|
-
|
|
9
|
+
auth;
|
|
10
|
+
constructor(config, auth) {
|
|
10
11
|
this.config = config;
|
|
12
|
+
this.auth = auth;
|
|
11
13
|
}
|
|
12
|
-
getClient() {
|
|
14
|
+
async getClient() {
|
|
13
15
|
if (this.client) return this.client;
|
|
14
|
-
|
|
15
|
-
const configuration = new Configuration({
|
|
16
|
+
this.client = new RemotePinningServiceClient(new Configuration({
|
|
16
17
|
endpointUrl: this.config.endpoint,
|
|
17
|
-
accessToken: this.
|
|
18
|
+
accessToken: await this.auth.getAccessToken(),
|
|
18
19
|
fetchApi: this.config.fetch ?? fetch
|
|
19
|
-
});
|
|
20
|
-
this.client = new RemotePinningServiceClient(configuration);
|
|
20
|
+
}));
|
|
21
21
|
return this.client;
|
|
22
22
|
}
|
|
23
23
|
async *add(cid, options) {
|
|
24
|
-
const client = this.getClient();
|
|
24
|
+
const client = await this.getClient();
|
|
25
25
|
const pin = {
|
|
26
26
|
cid: cid.toString(),
|
|
27
27
|
name: options?.name,
|
|
@@ -32,7 +32,7 @@ var PinClient = class {
|
|
|
32
32
|
yield cid;
|
|
33
33
|
}
|
|
34
34
|
async *ls(options) {
|
|
35
|
-
const response = await this.getClient().pinsGet(this.normalizeListOptions(options), { signal: options?.signal });
|
|
35
|
+
const response = await (await this.getClient()).pinsGet(this.normalizeListOptions(options), { signal: options?.signal });
|
|
36
36
|
for (const result of response.results) yield this.mapResponse(result);
|
|
37
37
|
}
|
|
38
38
|
async isPinned(cid, options) {
|
|
@@ -44,12 +44,12 @@ var PinClient = class {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
async get(cid, options) {
|
|
47
|
-
const response = await this.getClient().pinsGet({ cid: [cid.toString()] }, { signal: options?.signal });
|
|
47
|
+
const response = await (await this.getClient()).pinsGet({ cid: [cid.toString()] }, { signal: options?.signal });
|
|
48
48
|
if (response.results.length === 0) throw new NotFoundError(`Pin not found for CID: ${cid.toString()}`);
|
|
49
49
|
return this.mapResponse(response.results[0]);
|
|
50
50
|
}
|
|
51
51
|
async setMetadata(cid, metadata, options) {
|
|
52
|
-
const client = this.getClient();
|
|
52
|
+
const client = await this.getClient();
|
|
53
53
|
const response = await client.pinsGet({ cid: [cid.toString()] }, { signal: options?.signal });
|
|
54
54
|
if (response.results.length === 0) throw new NotFoundError(`Pin not found for CID: ${cid.toString()}`);
|
|
55
55
|
const pin = response.results[0];
|
|
@@ -64,14 +64,14 @@ var PinClient = class {
|
|
|
64
64
|
}, { signal: options?.signal });
|
|
65
65
|
}
|
|
66
66
|
async *rm(cid, options) {
|
|
67
|
-
const response = await this.getClient().pinsGet({ cid: [cid.toString()] }, { signal: options?.signal });
|
|
67
|
+
const response = await (await this.getClient()).pinsGet({ cid: [cid.toString()] }, { signal: options?.signal });
|
|
68
68
|
await Promise.all([...response.results].map(async (result) => {
|
|
69
69
|
return this.rmByRequestId(result.requestid, options);
|
|
70
70
|
}));
|
|
71
71
|
yield cid;
|
|
72
72
|
}
|
|
73
73
|
async rmByRequestId(requestId, options) {
|
|
74
|
-
await this.getClient().pinsRequestidDelete({ requestid: requestId }, { signal: options?.signal });
|
|
74
|
+
await (await this.getClient()).pinsRequestidDelete({ requestid: requestId }, { signal: options?.signal });
|
|
75
75
|
}
|
|
76
76
|
mapResponse(response) {
|
|
77
77
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":[],"sources":["../../../src/pin/client.ts"],"sourcesContent":["import {\n Configuration,\n type Pin,\n type PinStatus,\n RemotePinningServiceClient,\n} from \"@ipfs-shipyard/pinning-service-client\";\nimport type { PinnerConfig } from \"../config\";\nimport type {\n AbortOptions,\n RemoteAddOptions,\n RemoteLsOptions,\n RemotePin,\n RemotePins,\n} from \"@/types/pin\";\nimport { CID } from \"multiformats/cid\";\nimport { ConfigurationError, NotFoundError } from \"@/errors\";\n\nexport class PinClient implements RemotePins {\n private client: RemotePinningServiceClient | null = null;\n private config: PinnerConfig;\n\n constructor(config: PinnerConfig) {\n this.config = config;\n }\n\n protected getClient(): RemotePinningServiceClient {\n if (this.client) {\n return this.client;\n }\n\n
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../../../src/pin/client.ts"],"sourcesContent":["import {\n Configuration,\n type Pin,\n type PinStatus,\n RemotePinningServiceClient,\n} from \"@ipfs-shipyard/pinning-service-client\";\nimport type { PinnerConfig } from \"../config\";\nimport type { AuthManager } from \"@/auth\";\nimport type {\n AbortOptions,\n RemoteAddOptions,\n RemoteLsOptions,\n RemotePin,\n RemotePins,\n} from \"@/types/pin\";\nimport { CID } from \"multiformats/cid\";\nimport { ConfigurationError, NotFoundError } from \"@/errors\";\n\nexport class PinClient implements RemotePins {\n private client: RemotePinningServiceClient | null = null;\n private config: PinnerConfig;\n private readonly auth: AuthManager;\n\n constructor(config: PinnerConfig, auth: AuthManager) {\n this.config = config;\n this.auth = auth;\n }\n\n protected async getClient(): Promise<RemotePinningServiceClient> {\n if (this.client) {\n return this.client;\n }\n\n this.client = new RemotePinningServiceClient(\n new Configuration({\n endpointUrl: this.config.endpoint,\n accessToken: await this.auth.getAccessToken(),\n fetchApi: this.config.fetch ?? fetch,\n }),\n );\n return this.client;\n }\n\n async *add(\n cid: CID,\n options?: RemoteAddOptions,\n ): AsyncGenerator<CID, void, undefined> {\n const client = await this.getClient();\n\n const pin: Pin = {\n cid: cid.toString(),\n name: options?.name,\n meta: options?.metadata,\n origins: options?.origins,\n };\n\n await client.pinsPost({ pin }, { signal: options?.signal });\n\n yield cid;\n }\n\n async *ls(\n options?: RemoteLsOptions,\n ): AsyncGenerator<RemotePin, void, undefined> {\n const client = await this.getClient();\n const response = await client.pinsGet(this.normalizeListOptions(options), {\n signal: options?.signal,\n });\n\n for (const result of response.results) {\n yield this.mapResponse(result);\n }\n }\n\n async isPinned(cid: CID, options?: AbortOptions): Promise<boolean> {\n try {\n await this.get(cid, options);\n return true;\n } catch {\n return false;\n }\n }\n\n async get(cid: CID, options?: AbortOptions): Promise<RemotePin> {\n const client = await this.getClient();\n const response = await client.pinsGet(\n { cid: [cid.toString()] },\n {\n signal: options?.signal,\n },\n );\n\n if (response.results.length === 0) {\n throw new NotFoundError(`Pin not found for CID: ${cid.toString()}`);\n }\n\n return this.mapResponse(response.results[0]);\n }\n\n async setMetadata(\n cid: CID,\n metadata: Record<string, string> | undefined,\n options?: AbortOptions,\n ): Promise<void> {\n const client = await this.getClient();\n const response = await client.pinsGet(\n { cid: [cid.toString()] },\n {\n signal: options?.signal,\n },\n );\n\n if (response.results.length === 0) {\n throw new NotFoundError(`Pin not found for CID: ${cid.toString()}`);\n }\n\n const pin = response.results[0];\n await client.pinsRequestidPost(\n {\n requestid: pin.requestid,\n pin: {\n cid: pin.pin.cid,\n name: pin.pin.name,\n meta: metadata,\n origins: pin.pin.origins,\n },\n },\n { signal: options?.signal },\n );\n }\n\n async *rm(\n cid: CID,\n options?: AbortOptions,\n ): AsyncGenerator<CID, void, undefined> {\n const client = await this.getClient();\n const response = await client.pinsGet(\n { cid: [cid.toString()] },\n { signal: options?.signal },\n );\n\n // Delete all pins for this CID by their request IDs\n await Promise.all(\n [...response.results].map(async (result) => {\n return this.rmByRequestId(result.requestid, options);\n }),\n );\n\n yield cid;\n }\n\n async rmByRequestId(requestId: string, options?: AbortOptions): Promise<void> {\n const client = await this.getClient();\n await client.pinsRequestidDelete(\n { requestid: requestId },\n { signal: options?.signal },\n );\n }\n\n private mapResponse(response: PinStatus): RemotePin {\n return {\n cid: CID.parse(response.pin.cid),\n name: response.pin.name,\n status: response.status,\n created: response.created,\n size: response.pin.meta?.size\n ? parseInt(response.pin.meta.size, 10)\n : undefined,\n metadata: response.pin.meta,\n };\n }\n\n private normalizeListOptions(\n options?: RemoteLsOptions,\n ): Record<string, unknown> {\n const request: Record<string, unknown> = {};\n\n if (options?.limit !== undefined) {\n request.limit = options.limit;\n }\n if (options?.cursor !== undefined) {\n request.after = options.cursor;\n }\n if (options?.status !== undefined) {\n request.status = options.status;\n }\n if (options?.name !== undefined) {\n request.name = options.name;\n }\n\n return request;\n }\n}\n"],"mappings":"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"}
|
package/dist/esm/pinner.d.ts
CHANGED
|
@@ -14,6 +14,11 @@ declare class Pinner {
|
|
|
14
14
|
private _ipns;
|
|
15
15
|
private _websites;
|
|
16
16
|
private _upload?;
|
|
17
|
+
private readonly auth;
|
|
18
|
+
/**
|
|
19
|
+
* Create a new Pinner SDK instance.
|
|
20
|
+
* @param config SDK configuration object
|
|
21
|
+
*/
|
|
17
22
|
constructor(config: PinnerConfig);
|
|
18
23
|
/**
|
|
19
24
|
* Access the remote pins interface.
|
|
@@ -37,6 +42,8 @@ declare class Pinner {
|
|
|
37
42
|
/**
|
|
38
43
|
* Upload a file and wait for completion.
|
|
39
44
|
* Convenience method for simple use cases where controls aren't needed.
|
|
45
|
+
* @param file The file to upload
|
|
46
|
+
* @param options Upload configuration
|
|
40
47
|
*/
|
|
41
48
|
uploadAndWait(file: File, options?: UploadOptions): Promise<UploadResult>;
|
|
42
49
|
/**
|
|
@@ -48,39 +55,54 @@ declare class Pinner {
|
|
|
48
55
|
waitForOperation(input: number | UploadResult, options?: OperationPollingOptions): Promise<UploadResult>;
|
|
49
56
|
/**
|
|
50
57
|
* Upload a directory to IPFS.
|
|
58
|
+
* @param files Array of files to upload as a directory
|
|
59
|
+
* @param options Upload configuration
|
|
51
60
|
*/
|
|
52
61
|
uploadDirectory(files: File[], options?: UploadOptions): Promise<UploadOperation>;
|
|
53
62
|
/**
|
|
54
63
|
* Upload a CAR file without preprocessing.
|
|
55
64
|
* This is useful for passthrough of pre-generated CAR files.
|
|
65
|
+
* @param file CAR file or stream to upload
|
|
66
|
+
* @param options Upload configuration
|
|
56
67
|
*/
|
|
57
68
|
uploadCar(file: File | ReadableStream<Uint8Array>, options?: UploadOptions): Promise<UploadOperation>;
|
|
58
69
|
/**
|
|
59
70
|
* Pin existing content by CID.
|
|
71
|
+
* @param cid CID of content to pin (string or CID object)
|
|
72
|
+
* @param options Remote add options
|
|
60
73
|
*/
|
|
61
74
|
pinByHash(cid: string | CID, options?: RemoteAddOptions): Promise<AsyncGenerator<CID, void, undefined>>;
|
|
62
75
|
/**
|
|
63
76
|
* List pinned content.
|
|
77
|
+
* @param options List filtering options
|
|
64
78
|
*/
|
|
65
79
|
listPins(options?: RemoteLsOptions): Promise<RemotePin[]>;
|
|
66
80
|
/**
|
|
67
81
|
* Get pin status.
|
|
82
|
+
* @param cid CID of the pinned content to check
|
|
68
83
|
*/
|
|
69
84
|
getPinStatus(cid: string | CID): Promise<RemotePin>;
|
|
70
85
|
/**
|
|
71
86
|
* Check if content is pinned.
|
|
87
|
+
* @param cid CID to check
|
|
72
88
|
*/
|
|
73
89
|
isPinned(cid: string | CID): Promise<boolean>;
|
|
74
90
|
/**
|
|
75
91
|
* Update pin metadata.
|
|
92
|
+
* @param cid CID of the pin
|
|
93
|
+
* @param metadata Key-value metadata to set
|
|
76
94
|
*/
|
|
77
95
|
setPinMetadata(cid: string | CID, metadata: Record<string, string> | undefined): Promise<void>;
|
|
78
96
|
/**
|
|
79
97
|
* Remove a pin. The block may be deleted when garbage collection is run.
|
|
98
|
+
* @param cid CID to unpin
|
|
99
|
+
* @param options Abort options
|
|
80
100
|
*/
|
|
81
101
|
unpin(cid: string | CID, options?: AbortOptions): Promise<void>;
|
|
82
102
|
/**
|
|
83
103
|
* Remove a pin by request ID. The block may be deleted when garbage collection is run.
|
|
104
|
+
* @param requestId The request ID to remove
|
|
105
|
+
* @param options Abort options
|
|
84
106
|
*/
|
|
85
107
|
unpinByRequestId(requestId: string, options?: AbortOptions): Promise<void>;
|
|
86
108
|
/**
|
package/dist/esm/pinner.js
CHANGED
|
@@ -5,6 +5,9 @@ import { PinClient } from "./pin/client.js";
|
|
|
5
5
|
import "./pin/index.js";
|
|
6
6
|
import { IpnsClient } from "./api/ipns.js";
|
|
7
7
|
import { WebsitesClient } from "./api/websites.js";
|
|
8
|
+
import { KeyExchangeAuthManager } from "./auth/key-exchange.js";
|
|
9
|
+
import "./auth/index.js";
|
|
10
|
+
import { DEFAULT_CONFIG } from "./config.js";
|
|
8
11
|
import { CID } from "multiformats/cid";
|
|
9
12
|
|
|
10
13
|
//#region src/pinner.ts
|
|
@@ -14,11 +17,18 @@ var Pinner = class {
|
|
|
14
17
|
_ipns;
|
|
15
18
|
_websites;
|
|
16
19
|
_upload;
|
|
20
|
+
auth;
|
|
21
|
+
/**
|
|
22
|
+
* Create a new Pinner SDK instance.
|
|
23
|
+
* @param config SDK configuration object
|
|
24
|
+
*/
|
|
17
25
|
constructor(config) {
|
|
18
|
-
|
|
19
|
-
this.
|
|
20
|
-
this.
|
|
21
|
-
this.
|
|
26
|
+
const endpoint = config.endpoint ?? DEFAULT_CONFIG.endpoint;
|
|
27
|
+
this.auth = new KeyExchangeAuthManager(config.jwt, endpoint);
|
|
28
|
+
this.uploadManager = new UploadManager(config, this.auth);
|
|
29
|
+
this._pins = new PinClient(config, this.auth);
|
|
30
|
+
this._ipns = new IpnsClient(config, this.auth);
|
|
31
|
+
this._websites = new WebsitesClient(config, this.auth);
|
|
22
32
|
}
|
|
23
33
|
/**
|
|
24
34
|
* Access the remote pins interface.
|
|
@@ -60,6 +70,8 @@ var Pinner = class {
|
|
|
60
70
|
/**
|
|
61
71
|
* Upload a file and wait for completion.
|
|
62
72
|
* Convenience method for simple use cases where controls aren't needed.
|
|
73
|
+
* @param file The file to upload
|
|
74
|
+
* @param options Upload configuration
|
|
63
75
|
*/
|
|
64
76
|
async uploadAndWait(file, options) {
|
|
65
77
|
return (await this.upload(file, options)).result;
|
|
@@ -75,6 +87,8 @@ var Pinner = class {
|
|
|
75
87
|
}
|
|
76
88
|
/**
|
|
77
89
|
* Upload a directory to IPFS.
|
|
90
|
+
* @param files Array of files to upload as a directory
|
|
91
|
+
* @param options Upload configuration
|
|
78
92
|
*/
|
|
79
93
|
async uploadDirectory(files, options) {
|
|
80
94
|
return this.uploadManager.uploadDirectory(files, options);
|
|
@@ -82,12 +96,16 @@ var Pinner = class {
|
|
|
82
96
|
/**
|
|
83
97
|
* Upload a CAR file without preprocessing.
|
|
84
98
|
* This is useful for passthrough of pre-generated CAR files.
|
|
99
|
+
* @param file CAR file or stream to upload
|
|
100
|
+
* @param options Upload configuration
|
|
85
101
|
*/
|
|
86
102
|
async uploadCar(file, options) {
|
|
87
103
|
return this.uploadManager.uploadCar(file, options);
|
|
88
104
|
}
|
|
89
105
|
/**
|
|
90
106
|
* Pin existing content by CID.
|
|
107
|
+
* @param cid CID of content to pin (string or CID object)
|
|
108
|
+
* @param options Remote add options
|
|
91
109
|
*/
|
|
92
110
|
async pinByHash(cid, options) {
|
|
93
111
|
const cidObj = typeof cid === "string" ? CID.parse(cid) : cid;
|
|
@@ -95,6 +113,7 @@ var Pinner = class {
|
|
|
95
113
|
}
|
|
96
114
|
/**
|
|
97
115
|
* List pinned content.
|
|
116
|
+
* @param options List filtering options
|
|
98
117
|
*/
|
|
99
118
|
async listPins(options) {
|
|
100
119
|
const pins = [];
|
|
@@ -103,6 +122,7 @@ var Pinner = class {
|
|
|
103
122
|
}
|
|
104
123
|
/**
|
|
105
124
|
* Get pin status.
|
|
125
|
+
* @param cid CID of the pinned content to check
|
|
106
126
|
*/
|
|
107
127
|
async getPinStatus(cid) {
|
|
108
128
|
const cidObj = typeof cid === "string" ? CID.parse(cid) : cid;
|
|
@@ -110,6 +130,7 @@ var Pinner = class {
|
|
|
110
130
|
}
|
|
111
131
|
/**
|
|
112
132
|
* Check if content is pinned.
|
|
133
|
+
* @param cid CID to check
|
|
113
134
|
*/
|
|
114
135
|
async isPinned(cid) {
|
|
115
136
|
const cidObj = typeof cid === "string" ? CID.parse(cid) : cid;
|
|
@@ -117,6 +138,8 @@ var Pinner = class {
|
|
|
117
138
|
}
|
|
118
139
|
/**
|
|
119
140
|
* Update pin metadata.
|
|
141
|
+
* @param cid CID of the pin
|
|
142
|
+
* @param metadata Key-value metadata to set
|
|
120
143
|
*/
|
|
121
144
|
async setPinMetadata(cid, metadata) {
|
|
122
145
|
const cidObj = typeof cid === "string" ? CID.parse(cid) : cid;
|
|
@@ -124,6 +147,8 @@ var Pinner = class {
|
|
|
124
147
|
}
|
|
125
148
|
/**
|
|
126
149
|
* Remove a pin. The block may be deleted when garbage collection is run.
|
|
150
|
+
* @param cid CID to unpin
|
|
151
|
+
* @param options Abort options
|
|
127
152
|
*/
|
|
128
153
|
async unpin(cid, options) {
|
|
129
154
|
const cidObj = typeof cid === "string" ? CID.parse(cid) : cid;
|
|
@@ -132,6 +157,8 @@ var Pinner = class {
|
|
|
132
157
|
}
|
|
133
158
|
/**
|
|
134
159
|
* Remove a pin by request ID. The block may be deleted when garbage collection is run.
|
|
160
|
+
* @param requestId The request ID to remove
|
|
161
|
+
* @param options Abort options
|
|
135
162
|
*/
|
|
136
163
|
async unpinByRequestId(requestId, options) {
|
|
137
164
|
return this.pins.rmByRequestId(requestId, options);
|
package/dist/esm/pinner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pinner.js","names":[],"sources":["../../src/pinner.ts"],"sourcesContent":["import type { PinnerConfig } from \"./config\";\nimport { UploadManager } from \"./upload\";\nimport { PinClient } from \"./pin\";\nimport { IpnsClient } from \"./api/ipns\";\nimport { WebsitesClient } from \"./api/websites\";\nimport type { UploadMethodAndBuilder } from \"@/upload/builder\";\nimport { createUploadBuilderNamespace } from \"@/upload/builder\";\nimport type {\n UploadOperation,\n UploadOptions,\n UploadResult,\n} from \"@/types/upload\";\nimport type { OperationPollingOptions } from \"@lumeweb/portal-sdk\";\nimport type {\n AbortOptions,\n RemoteAddOptions,\n RemoteLsOptions,\n RemotePin,\n RemotePins,\n} from \"@/types/pin\";\nimport { CID } from \"multiformats/cid\";\n\nexport class Pinner {\n private uploadManager: UploadManager;\n private _pins: RemotePins;\n private _ipns: IpnsClient;\n private _websites: WebsitesClient;\n private _upload?: UploadMethodAndBuilder;\n\n constructor(config: PinnerConfig) {\n this.uploadManager = new UploadManager(config);\n this._pins = new PinClient(config);\n this._ipns = new IpnsClient(config);\n this._websites = new WebsitesClient(config);\n }\n\n /**\n * Access the remote pins interface.\n */\n get pins(): RemotePins {\n return this._pins;\n }\n\n /**\n * Access the IPNS interface for key management and publishing.\n */\n get ipns(): IpnsClient {\n return this._ipns;\n }\n\n /**\n * Access the websites interface for website configuration and management.\n */\n get websites(): WebsitesClient {\n return this._websites;\n }\n\n /**\n * Upload interface that works as both a method and a builder namespace.\n *\n * As a method: upload(file, options) -> UploadOperation\n * As a property: upload.file(), upload.json(), etc. -> Builder\n */\n get upload(): UploadMethodAndBuilder {\n if (!this._upload) {\n const builderNamespace = createUploadBuilderNamespace(this);\n const uploadMethod = async (\n file: File,\n options?: UploadOptions,\n ): Promise<UploadOperation> => {\n return this.uploadManager.upload(file, options);\n };\n\n this._upload = new Proxy(uploadMethod, {\n get(target, prop) {\n if (prop in builderNamespace) {\n return Reflect.get(builderNamespace, prop);\n }\n return Reflect.get(target, prop);\n },\n }) as UploadMethodAndBuilder;\n }\n return this._upload;\n }\n\n /**\n * Upload a file and wait for completion.\n * Convenience method for simple use cases where controls aren't needed.\n */\n async uploadAndWait(\n file: File,\n options?: UploadOptions,\n ): Promise<UploadResult> {\n const operation = await this.upload(file, options);\n return operation.result;\n }\n\n /**\n * Wait for an operation to complete or reach a settled state.\n * @param input Either an operation ID (number) or an UploadResult\n * @param options Polling options (interval, timeout, settledStates)\n * @returns UploadResult with operation status merged in\n */\n async waitForOperation(\n input: number | UploadResult,\n options?: OperationPollingOptions,\n ): Promise<UploadResult> {\n return this.uploadManager.waitForOperation(input, options);\n }\n\n /**\n * Upload a directory to IPFS.\n */\n async uploadDirectory(\n files: File[],\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n return this.uploadManager.uploadDirectory(files, options);\n }\n\n /**\n * Upload a CAR file without preprocessing.\n * This is useful for passthrough of pre-generated CAR files.\n */\n async uploadCar(\n file: File | ReadableStream<Uint8Array>,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n return this.uploadManager.uploadCar(file, options);\n }\n\n /**\n * Pin existing content by CID.\n */\n async pinByHash(\n cid: string | CID,\n options?: RemoteAddOptions,\n ): Promise<AsyncGenerator<CID, void, undefined>> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n return this.pins.add(cidObj, options);\n }\n\n /**\n * List pinned content.\n */\n async listPins(options?: RemoteLsOptions): Promise<RemotePin[]> {\n const pins: RemotePin[] = [];\n for await (const pin of this.pins.ls(options)) {\n pins.push(pin);\n }\n return pins;\n }\n\n /**\n * Get pin status.\n */\n async getPinStatus(cid: string | CID): Promise<RemotePin> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n return this.pins.get(cidObj);\n }\n\n /**\n * Check if content is pinned.\n */\n async isPinned(cid: string | CID): Promise<boolean> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n return this.pins.isPinned(cidObj);\n }\n\n /**\n * Update pin metadata.\n */\n async setPinMetadata(\n cid: string | CID,\n metadata: Record<string, string> | undefined,\n ): Promise<void> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n return this.pins.setMetadata(cidObj, metadata);\n }\n\n /**\n * Remove a pin. The block may be deleted when garbage collection is run.\n */\n async unpin(cid: string | CID, options?: AbortOptions): Promise<void> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n const generator = this.pins.rm(cidObj, options);\n for await (const _ of generator) {\n // Consume the generator to complete the unpin operation\n }\n }\n\n /**\n * Remove a pin by request ID. The block may be deleted when garbage collection is run.\n */\n async unpinByRequestId(requestId: string, options?: AbortOptions): Promise<void> {\n return this.pins.rmByRequestId(requestId, options);\n }\n\n /**\n * Destroy the client and cleanup resources.\n */\n destroy(): void {\n this.uploadManager.destroy();\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"pinner.js","names":[],"sources":["../../src/pinner.ts"],"sourcesContent":["import type { PinnerConfig } from \"./config\";\nimport { UploadManager } from \"./upload\";\nimport { PinClient } from \"./pin\";\nimport { IpnsClient } from \"./api/ipns\";\nimport { WebsitesClient } from \"./api/websites\";\nimport { JwtAuthManager, KeyExchangeAuthManager, type AuthManager } from \"@/auth\";\nimport { DEFAULT_CONFIG } from \"./config\";\nimport type { UploadMethodAndBuilder } from \"@/upload/builder\";\nimport { createUploadBuilderNamespace } from \"@/upload/builder\";\nimport type {\n UploadOperation,\n UploadOptions,\n UploadResult,\n} from \"@/types/upload\";\nimport type { OperationPollingOptions } from \"@lumeweb/portal-sdk\";\nimport type {\n AbortOptions,\n RemoteAddOptions,\n RemoteLsOptions,\n RemotePin,\n RemotePins,\n} from \"@/types/pin\";\nimport { CID } from \"multiformats/cid\";\n\nexport class Pinner {\n private uploadManager: UploadManager;\n private _pins: RemotePins;\n private _ipns: IpnsClient;\n private _websites: WebsitesClient;\n private _upload?: UploadMethodAndBuilder;\n private readonly auth: AuthManager;\n\n /**\n * Create a new Pinner SDK instance.\n * @param config SDK configuration object\n */\n constructor(config: PinnerConfig) {\n const endpoint = config.endpoint ?? DEFAULT_CONFIG.endpoint!;\n this.auth = new KeyExchangeAuthManager(config.jwt, endpoint);\n this.uploadManager = new UploadManager(config, this.auth);\n this._pins = new PinClient(config, this.auth);\n this._ipns = new IpnsClient(config, this.auth);\n this._websites = new WebsitesClient(config, this.auth);\n }\n\n /**\n * Access the remote pins interface.\n */\n get pins(): RemotePins {\n return this._pins;\n }\n\n /**\n * Access the IPNS interface for key management and publishing.\n */\n get ipns(): IpnsClient {\n return this._ipns;\n }\n\n /**\n * Access the websites interface for website configuration and management.\n */\n get websites(): WebsitesClient {\n return this._websites;\n }\n\n /**\n * Upload interface that works as both a method and a builder namespace.\n *\n * As a method: upload(file, options) -> UploadOperation\n * As a property: upload.file(), upload.json(), etc. -> Builder\n */\n get upload(): UploadMethodAndBuilder {\n if (!this._upload) {\n const builderNamespace = createUploadBuilderNamespace(this);\n const uploadMethod = async (\n file: File,\n options?: UploadOptions,\n ): Promise<UploadOperation> => {\n return this.uploadManager.upload(file, options);\n };\n\n this._upload = new Proxy(uploadMethod, {\n get(target, prop) {\n if (prop in builderNamespace) {\n return Reflect.get(builderNamespace, prop);\n }\n return Reflect.get(target, prop);\n },\n }) as UploadMethodAndBuilder;\n }\n return this._upload;\n }\n\n /**\n * Upload a file and wait for completion.\n * Convenience method for simple use cases where controls aren't needed.\n * @param file The file to upload\n * @param options Upload configuration\n */\n async uploadAndWait(\n file: File,\n options?: UploadOptions,\n ): Promise<UploadResult> {\n const operation = await this.upload(file, options);\n return operation.result;\n }\n\n /**\n * Wait for an operation to complete or reach a settled state.\n * @param input Either an operation ID (number) or an UploadResult\n * @param options Polling options (interval, timeout, settledStates)\n * @returns UploadResult with operation status merged in\n */\n async waitForOperation(\n input: number | UploadResult,\n options?: OperationPollingOptions,\n ): Promise<UploadResult> {\n return this.uploadManager.waitForOperation(input, options);\n }\n\n /**\n * Upload a directory to IPFS.\n * @param files Array of files to upload as a directory\n * @param options Upload configuration\n */\n async uploadDirectory(\n files: File[],\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n return this.uploadManager.uploadDirectory(files, options);\n }\n\n /**\n * Upload a CAR file without preprocessing.\n * This is useful for passthrough of pre-generated CAR files.\n * @param file CAR file or stream to upload\n * @param options Upload configuration\n */\n async uploadCar(\n file: File | ReadableStream<Uint8Array>,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n return this.uploadManager.uploadCar(file, options);\n }\n\n /**\n * Pin existing content by CID.\n * @param cid CID of content to pin (string or CID object)\n * @param options Remote add options\n */\n async pinByHash(\n cid: string | CID,\n options?: RemoteAddOptions,\n ): Promise<AsyncGenerator<CID, void, undefined>> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n return this.pins.add(cidObj, options);\n }\n\n /**\n * List pinned content.\n * @param options List filtering options\n */\n async listPins(options?: RemoteLsOptions): Promise<RemotePin[]> {\n const pins: RemotePin[] = [];\n for await (const pin of this.pins.ls(options)) {\n pins.push(pin);\n }\n return pins;\n }\n\n /**\n * Get pin status.\n * @param cid CID of the pinned content to check\n */\n async getPinStatus(cid: string | CID): Promise<RemotePin> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n return this.pins.get(cidObj);\n }\n\n /**\n * Check if content is pinned.\n * @param cid CID to check\n */\n async isPinned(cid: string | CID): Promise<boolean> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n return this.pins.isPinned(cidObj);\n }\n\n /**\n * Update pin metadata.\n * @param cid CID of the pin\n * @param metadata Key-value metadata to set\n */\n async setPinMetadata(\n cid: string | CID,\n metadata: Record<string, string> | undefined,\n ): Promise<void> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n return this.pins.setMetadata(cidObj, metadata);\n }\n\n /**\n * Remove a pin. The block may be deleted when garbage collection is run.\n * @param cid CID to unpin\n * @param options Abort options\n */\n async unpin(cid: string | CID, options?: AbortOptions): Promise<void> {\n const cidObj = typeof cid === \"string\" ? CID.parse(cid) : cid;\n const generator = this.pins.rm(cidObj, options);\n for await (const _ of generator) {\n // Consume the generator to complete the unpin operation\n }\n }\n\n /**\n * Remove a pin by request ID. The block may be deleted when garbage collection is run.\n * @param requestId The request ID to remove\n * @param options Abort options\n */\n async unpinByRequestId(requestId: string, options?: AbortOptions): Promise<void> {\n return this.pins.rmByRequestId(requestId, options);\n }\n\n /**\n * Destroy the client and cleanup resources.\n */\n destroy(): void {\n this.uploadManager.destroy();\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAwBA,IAAa,SAAb,MAAoB;CAClB,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAiB;;;;;CAMjB,YAAY,QAAsB;EAChC,MAAM,WAAW,OAAO,YAAY,eAAe;AACnD,OAAK,OAAO,IAAI,uBAAuB,OAAO,KAAK,SAAS;AAC5D,OAAK,gBAAgB,IAAI,cAAc,QAAQ,KAAK,KAAK;AACzD,OAAK,QAAQ,IAAI,UAAU,QAAQ,KAAK,KAAK;AAC7C,OAAK,QAAQ,IAAI,WAAW,QAAQ,KAAK,KAAK;AAC9C,OAAK,YAAY,IAAI,eAAe,QAAQ,KAAK,KAAK;;;;;CAMxD,IAAI,OAAmB;AACrB,SAAO,KAAK;;;;;CAMd,IAAI,OAAmB;AACrB,SAAO,KAAK;;;;;CAMd,IAAI,WAA2B;AAC7B,SAAO,KAAK;;;;;;;;CASd,IAAI,SAAiC;AACnC,MAAI,CAAC,KAAK,SAAS;GACjB,MAAM,mBAAmB,6BAA6B,KAAK;GAC3D,MAAM,eAAe,OACnB,MACA,YAC6B;AAC7B,WAAO,KAAK,cAAc,OAAO,MAAM,QAAQ;;AAGjD,QAAK,UAAU,IAAI,MAAM,cAAc,EACrC,IAAI,QAAQ,MAAM;AAChB,QAAI,QAAQ,iBACV,QAAO,QAAQ,IAAI,kBAAkB,KAAK;AAE5C,WAAO,QAAQ,IAAI,QAAQ,KAAK;MAEnC,CAAC;;AAEJ,SAAO,KAAK;;;;;;;;CASd,MAAM,cACJ,MACA,SACuB;AAEvB,UAAO,MADiB,KAAK,OAAO,MAAM,QAAQ,EACjC;;;;;;;;CASnB,MAAM,iBACJ,OACA,SACuB;AACvB,SAAO,KAAK,cAAc,iBAAiB,OAAO,QAAQ;;;;;;;CAQ5D,MAAM,gBACJ,OACA,SAC0B;AAC1B,SAAO,KAAK,cAAc,gBAAgB,OAAO,QAAQ;;;;;;;;CAS3D,MAAM,UACJ,MACA,SAC0B;AAC1B,SAAO,KAAK,cAAc,UAAU,MAAM,QAAQ;;;;;;;CAQpD,MAAM,UACJ,KACA,SAC+C;EAC/C,MAAM,SAAS,OAAO,QAAQ,WAAW,IAAI,MAAM,IAAI,GAAG;AAC1D,SAAO,KAAK,KAAK,IAAI,QAAQ,QAAQ;;;;;;CAOvC,MAAM,SAAS,SAAiD;EAC9D,MAAM,OAAoB,EAAE;AAC5B,aAAW,MAAM,OAAO,KAAK,KAAK,GAAG,QAAQ,CAC3C,MAAK,KAAK,IAAI;AAEhB,SAAO;;;;;;CAOT,MAAM,aAAa,KAAuC;EACxD,MAAM,SAAS,OAAO,QAAQ,WAAW,IAAI,MAAM,IAAI,GAAG;AAC1D,SAAO,KAAK,KAAK,IAAI,OAAO;;;;;;CAO9B,MAAM,SAAS,KAAqC;EAClD,MAAM,SAAS,OAAO,QAAQ,WAAW,IAAI,MAAM,IAAI,GAAG;AAC1D,SAAO,KAAK,KAAK,SAAS,OAAO;;;;;;;CAQnC,MAAM,eACJ,KACA,UACe;EACf,MAAM,SAAS,OAAO,QAAQ,WAAW,IAAI,MAAM,IAAI,GAAG;AAC1D,SAAO,KAAK,KAAK,YAAY,QAAQ,SAAS;;;;;;;CAQhD,MAAM,MAAM,KAAmB,SAAuC;EACpE,MAAM,SAAS,OAAO,QAAQ,WAAW,IAAI,MAAM,IAAI,GAAG;EAC1D,MAAM,YAAY,KAAK,KAAK,GAAG,QAAQ,QAAQ;AAC/C,aAAW,MAAM,KAAK;;;;;;;CAUxB,MAAM,iBAAiB,WAAmB,SAAuC;AAC/E,SAAO,KAAK,KAAK,cAAc,WAAW,QAAQ;;;;;CAMpD,UAAgB;AACd,OAAK,cAAc,SAAS"}
|
package/dist/esm/types/pin.d.ts
CHANGED
|
@@ -6,34 +6,76 @@ import { Status } from "@ipfs-shipyard/pinning-service-client";
|
|
|
6
6
|
* Options that can be passed to abort async operations
|
|
7
7
|
*/
|
|
8
8
|
interface AbortOptions {
|
|
9
|
+
/**
|
|
10
|
+
* AbortSignal to cancel the operation
|
|
11
|
+
*/
|
|
9
12
|
signal?: AbortSignal;
|
|
10
13
|
}
|
|
11
14
|
/**
|
|
12
15
|
* Allows passing extra options accepted by the remote pinning service
|
|
13
16
|
*/
|
|
14
17
|
interface RemoteAddOptions extends AbortOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Name for the pin or filter
|
|
20
|
+
*/
|
|
15
21
|
name?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Key-value metadata to attach to the pin
|
|
24
|
+
*/
|
|
16
25
|
metadata?: Record<string, string>;
|
|
26
|
+
/**
|
|
27
|
+
* Origin addresses for the pinned content
|
|
28
|
+
*/
|
|
17
29
|
origins?: string[];
|
|
18
30
|
}
|
|
19
31
|
/**
|
|
20
32
|
* Allows passing extra options accepted by the remote pinning service
|
|
21
33
|
*/
|
|
22
34
|
interface RemoteLsOptions extends AbortOptions {
|
|
35
|
+
/**
|
|
36
|
+
* Name for the pin or filter
|
|
37
|
+
*/
|
|
23
38
|
name?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Current status (queued, pinning, pinned, failed)
|
|
41
|
+
*/
|
|
24
42
|
status?: Status[];
|
|
43
|
+
/**
|
|
44
|
+
* Maximum number of results per page
|
|
45
|
+
*/
|
|
25
46
|
limit?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Pagination cursor for listing results
|
|
49
|
+
*/
|
|
26
50
|
cursor?: string;
|
|
27
51
|
}
|
|
28
52
|
/**
|
|
29
53
|
* Includes extra metadata supported by the remote pinning service
|
|
30
54
|
*/
|
|
31
55
|
interface RemotePin {
|
|
56
|
+
/**
|
|
57
|
+
* IPFS Content Identifier
|
|
58
|
+
*/
|
|
32
59
|
cid: CID;
|
|
60
|
+
/**
|
|
61
|
+
* Name for the pin or filter
|
|
62
|
+
*/
|
|
33
63
|
name?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Current status (queued, pinning, pinned, failed)
|
|
66
|
+
*/
|
|
34
67
|
status: Status;
|
|
68
|
+
/**
|
|
69
|
+
* ISO timestamp of creation
|
|
70
|
+
*/
|
|
35
71
|
created: Date;
|
|
72
|
+
/**
|
|
73
|
+
* Size in bytes
|
|
74
|
+
*/
|
|
36
75
|
size?: number;
|
|
76
|
+
/**
|
|
77
|
+
* Key-value metadata to attach to the pin
|
|
78
|
+
*/
|
|
37
79
|
metadata?: Record<string, string>;
|
|
38
80
|
}
|
|
39
81
|
/**
|
|
@@ -9,18 +9,20 @@ import defer from "p-defer";
|
|
|
9
9
|
//#region src/upload/base-upload.ts
|
|
10
10
|
var BaseUploadHandler = class {
|
|
11
11
|
config;
|
|
12
|
-
|
|
12
|
+
auth;
|
|
13
|
+
constructor(config, auth) {
|
|
13
14
|
this.config = config;
|
|
15
|
+
this.auth = auth;
|
|
14
16
|
}
|
|
15
17
|
async upload(input, options) {
|
|
16
18
|
const normalized = normalizeUploadInput(input, options);
|
|
17
19
|
const uppy = new Uppy();
|
|
18
|
-
const { fileId, resultPromise, progress } = this.#setupUppyHandlers(uppy, normalized, options);
|
|
20
|
+
const { fileId, resultPromise, progress } = await this.#setupUppyHandlers(uppy, normalized, options);
|
|
19
21
|
await this.#addFileToUppy(uppy, normalized, normalized.size);
|
|
20
22
|
this.#startUpload(uppy, options);
|
|
21
23
|
return this.#createUploadOperation(uppy, fileId, resultPromise, progress);
|
|
22
24
|
}
|
|
23
|
-
#setupUppyHandlers(uppy, normalized, options) {
|
|
25
|
+
async #setupUppyHandlers(uppy, normalized, options) {
|
|
24
26
|
let fileId = null;
|
|
25
27
|
let hasRejected = false;
|
|
26
28
|
const progress = {
|
|
@@ -35,7 +37,7 @@ var BaseUploadHandler = class {
|
|
|
35
37
|
options?.onError?.(error);
|
|
36
38
|
rejectResult(error);
|
|
37
39
|
};
|
|
38
|
-
this.configurePlugin(uppy);
|
|
40
|
+
await this.configurePlugin(uppy);
|
|
39
41
|
uppy.on("progress", (progressBytes) => {
|
|
40
42
|
progress.bytesUploaded = progressBytes;
|
|
41
43
|
progress.percentage = progressBytes / progress.bytesTotal * 100;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-upload.js","names":["#setupUppyHandlers","#addFileToUppy","#startUpload","#createUploadOperation","#extractErrorMessage","#normalizeData","#normalizeDataForNode","#normalizeDataForBrowser"],"sources":["../../../src/upload/base-upload.ts"],"sourcesContent":["import Uppy from \"@uppy/core\";\nimport { default as defer } from \"p-defer\";\nimport type { Readable } from \"stream\";\n\nimport type { PinnerConfig } from \"../config\";\nimport type {\n UploadInput,\n UploadOperation,\n UploadOptions,\n UploadProgress,\n UploadResult,\n} from \"@/types/upload\";\nimport { UploadResultSymbol } from \"@/types/upload\";\nimport type { PostUploadResponse } from \"@/api/generated/schemas\";\nimport { normalizeUploadInput, type UploadInputObject } from \"./normalize\";\nimport {\n fileToReadableStream,\n readableStreamToNodeStream,\n} from \"@/utils/stream\";\nimport { isNodeEnvironment } from \"@/utils/env\";\nimport { UPLOAD_SOURCE_TUS, UPLOAD_SOURCE_XHR } from \"./constants\";\n\n// Node.js Readable stream with size property for Uppy compatibility\ntype NodeStreamWithSize = Readable & { size: number | null };\n\nexport abstract class BaseUploadHandler {\n protected config: Required<PinnerConfig>;\n\n constructor(config: PinnerConfig) {\n this.config = config as Required<PinnerConfig>;\n }\n\n async upload(\n input: UploadInput | UploadInputObject,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n const normalized = normalizeUploadInput(input, options);\n const uppy = new Uppy();\n const { fileId, resultPromise, progress } = this.#setupUppyHandlers(\n uppy,\n normalized,\n options,\n );\n\n await this.#addFileToUppy(uppy, normalized, normalized.size);\n this.#startUpload(uppy, options);\n\n return this.#createUploadOperation(uppy, fileId, resultPromise, progress);\n }\n\n #setupUppyHandlers(\n uppy: Uppy,\n normalized: { size: number },\n options?: UploadOptions,\n ): {\n fileId: string | null;\n resultPromise: Promise<UploadResult>;\n progress: UploadProgress;\n } {\n let fileId: string | null = null;\n let hasRejected = false;\n\n const progress: UploadProgress = {\n percentage: 0,\n bytesUploaded: 0,\n bytesTotal: normalized.size,\n };\n\n const {\n promise: resultPromise,\n resolve: resolveResult,\n reject: rejectResult,\n } = defer<UploadResult>();\n\n const handleError = (error: Error) => {\n if (hasRejected) return;\n hasRejected = true;\n options?.onError?.(error);\n rejectResult(error);\n };\n\n this.configurePlugin(uppy);\n\n uppy.on(\"progress\", (progressBytes) => {\n progress.bytesUploaded = progressBytes;\n progress.percentage = (progressBytes / progress.bytesTotal) * 100;\n options?.onProgress?.(progress);\n });\n\n uppy.on(\"upload-success\", (_file, result) => {\n if (hasRejected) return;\n const uploadResult = this.parseResult(result);\n options?.onComplete?.(uploadResult);\n resolveResult(uploadResult);\n });\n\n uppy.on(\"error\", (error) => {\n handleError(new Error(this.#extractErrorMessage(error)));\n });\n\n uppy.on(\"file-added\", (file) => {\n fileId = file.id;\n });\n\n return { fileId, resultPromise, progress };\n }\n\n #extractErrorMessage(error: unknown): string {\n let errorMessage = \"Upload fainormalizeDataled\";\n\n if (!error) return errorMessage;\n\n if (typeof error === \"string\") {\n errorMessage = error;\n } else if (error instanceof Error) {\n errorMessage = error.message;\n } else if ((error as any).message) {\n errorMessage = (error as any).message;\n } else if (\n (error as any).toString &&\n typeof (error as any).toString === \"function\"\n ) {\n errorMessage = (error as any).toString();\n }\n\n // Try to extract error from response (XHRUpload/TusPlugin)\n const errorObj = error as any;\n if (errorObj?.xhr?.response) {\n try {\n const response =\n typeof errorObj.xhr.response === \"string\"\n ? JSON.parse(errorObj.xhr.response)\n : errorObj.xhr.response;\n if (response.error) {\n errorMessage = response.error;\n } else if (response.message) {\n errorMessage = response.message;\n }\n } catch {\n // If parsing fails, use the original error message\n }\n }\n\n return errorMessage;\n }\n\n async #normalizeData(\n data: File | ReadableStream<Uint8Array>,\n ): Promise<File | ReadableStream<Uint8Array> | NodeStreamWithSize | Blob> {\n if (isNodeEnvironment()) {\n return this.#normalizeDataForNode(data);\n }\n return this.#normalizeDataForBrowser(data);\n }\n\n async #normalizeDataForBrowser(\n data: File | ReadableStream<Uint8Array>,\n ): Promise<File | Blob | ReadableStream<Uint8Array>> {\n // TUS plugin requires File, Blob, or Reader in browser\n if (this.getUploadSource() === UPLOAD_SOURCE_TUS) {\n if (data instanceof ReadableStream) {\n const { streamToBlobViaResponse } = await import(\"@/utils/stream\");\n return streamToBlobViaResponse(data);\n }\n return data;\n }\n\n // XHRUpload handles File/Blob directly\n return data;\n }\n\n async #normalizeDataForNode(\n data: File | ReadableStream<Uint8Array>,\n ): Promise<NodeStreamWithSize | File | Blob | ReadableStream<Uint8Array>> {\n // XHRUpload with formData: true requires Blob/File for FormData.append()\n // Do not convert to Node.js stream for XHRUpload\n if (this.getUploadSource() === UPLOAD_SOURCE_XHR) {\n return data;\n }\n\n // Convert File to ReadableStream without loading entire blob into memory\n if (data instanceof File) {\n const stream = fileToReadableStream(data);\n // Convert to Node.js stream for tus-js-client\n const nodeStream = await readableStreamToNodeStream(stream);\n return nodeStream as NodeStreamWithSize;\n }\n\n // In Node.js, convert ReadableStream to Node.js stream.Readable for tus-js-client's NodeFileReader\n if (data instanceof ReadableStream) {\n const nodeStream = await readableStreamToNodeStream(data);\n // Add size property to satisfy Uppy's type requirements\n return nodeStream as NodeStreamWithSize;\n }\n\n return data;\n }\n\n async #addFileToUppy(\n uppy: Uppy,\n normalized: {\n data: File | ReadableStream<Uint8Array>;\n name: string;\n type: string;\n },\n size?: number,\n ): Promise<void> {\n const fileData = await this.#normalizeData(normalized.data);\n const fileOptions = {\n source: this.getUploadSource(),\n name: normalized.name,\n type: normalized.type,\n data: fileData as any,\n };\n\n // Add file to Uppy first\n // Note: Uppy accepts any data type as it defers to the drivers\n const fileId = uppy.addFile(fileOptions);\n\n // Set TUS upload size if provided\n // In Node.js, streams need explicit size for tus-js-client\n // In browser, Uppy's TUS plugin may not derive size from Blob automatically\n if (\n this.getUploadSource() === UPLOAD_SOURCE_TUS &&\n size !== undefined &&\n size > 0\n ) {\n uppy.setFileState(fileId, {\n tus: { uploadSize: size },\n });\n }\n }\n\n #startUpload(uppy: Uppy, options?: UploadOptions): void {\n uppy.upload().catch((error) => {\n options?.onError?.(new Error(this.#extractErrorMessage(error)));\n });\n }\n\n #createUploadOperation(\n uppy: Uppy,\n fileId: string | null,\n resultPromise: Promise<UploadResult>,\n progress: UploadProgress,\n ): UploadOperation {\n return {\n cancel: () => {\n uppy.cancelAll();\n },\n pause: () => {\n if (fileId) {\n uppy.pauseResume(fileId);\n }\n },\n resume: () => {\n if (fileId) {\n uppy.pauseResume(fileId);\n }\n },\n result: resultPromise,\n progress: Object.freeze({ ...progress }),\n };\n }\n\n destroy(): void {\n // No-op since each upload creates its own Uppy instance\n }\n\n protected abstract configurePlugin(uppy: Uppy): void;\n protected abstract parseResult(result: unknown): UploadResult;\n protected abstract getUploadSource(): string;\n\n protected mapUploadResponse(\n body: unknown,\n uploadId: string,\n overrides?: Partial<UploadResult>,\n ): UploadResult {\n if (body && typeof body === \"object\" && \"CID\" in body) {\n const { CID } = body as PostUploadResponse;\n return {\n id: uploadId,\n cid: CID,\n name: \"\",\n size: 0,\n mimeType: \"\",\n createdAt: new Date(),\n numberOfFiles: 1,\n [UploadResultSymbol]: true,\n ...overrides,\n };\n }\n\n return {\n id: uploadId,\n name: \"\",\n size: 0,\n mimeType: \"\",\n createdAt: new Date(),\n numberOfFiles: 1,\n [UploadResultSymbol]: true,\n ...overrides,\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base-upload.js","names":["#setupUppyHandlers","#addFileToUppy","#startUpload","#createUploadOperation","#extractErrorMessage","#normalizeData","#normalizeDataForNode","#normalizeDataForBrowser"],"sources":["../../../src/upload/base-upload.ts"],"sourcesContent":["import Uppy from \"@uppy/core\";\nimport { default as defer } from \"p-defer\";\nimport type { Readable } from \"stream\";\n\nimport type { PinnerConfig } from \"../config\";\nimport type { AuthManager } from \"@/auth\";\nimport type {\n UploadInput,\n UploadOperation,\n UploadOptions,\n UploadProgress,\n UploadResult,\n} from \"@/types/upload\";\nimport { UploadResultSymbol } from \"@/types/upload\";\nimport type { PostUploadResponse } from \"@/api/generated/schemas\";\nimport { normalizeUploadInput, type UploadInputObject } from \"./normalize\";\nimport {\n fileToReadableStream,\n readableStreamToNodeStream,\n} from \"@/utils/stream\";\nimport { isNodeEnvironment } from \"@/utils/env\";\nimport { UPLOAD_SOURCE_TUS, UPLOAD_SOURCE_XHR } from \"./constants\";\n\n// Node.js Readable stream with size property for Uppy compatibility\ntype NodeStreamWithSize = Readable & { size: number | null };\n\nexport abstract class BaseUploadHandler {\n protected config: Required<PinnerConfig>;\n protected readonly auth: AuthManager;\n\n constructor(config: PinnerConfig, auth: AuthManager) {\n this.config = config as Required<PinnerConfig>;\n this.auth = auth;\n }\n\n async upload(\n input: UploadInput | UploadInputObject,\n options?: UploadOptions,\n ): Promise<UploadOperation> {\n const normalized = normalizeUploadInput(input, options);\n const uppy = new Uppy();\n const { fileId, resultPromise, progress } = await this.#setupUppyHandlers(\n uppy,\n normalized,\n options,\n );\n\n await this.#addFileToUppy(uppy, normalized, normalized.size);\n this.#startUpload(uppy, options);\n\n return this.#createUploadOperation(uppy, fileId, resultPromise, progress);\n }\n\n async #setupUppyHandlers(\n uppy: Uppy,\n normalized: { size: number },\n options?: UploadOptions,\n ): Promise<{\n fileId: string | null;\n resultPromise: Promise<UploadResult>;\n progress: UploadProgress;\n }> {\n let fileId: string | null = null;\n let hasRejected = false;\n\n const progress: UploadProgress = {\n percentage: 0,\n bytesUploaded: 0,\n bytesTotal: normalized.size,\n };\n\n const {\n promise: resultPromise,\n resolve: resolveResult,\n reject: rejectResult,\n } = defer<UploadResult>();\n\n const handleError = (error: Error) => {\n if (hasRejected) return;\n hasRejected = true;\n options?.onError?.(error);\n rejectResult(error);\n };\n\n await this.configurePlugin(uppy);\n\n uppy.on(\"progress\", (progressBytes) => {\n progress.bytesUploaded = progressBytes;\n progress.percentage = (progressBytes / progress.bytesTotal) * 100;\n options?.onProgress?.(progress);\n });\n\n uppy.on(\"upload-success\", (_file, result) => {\n if (hasRejected) return;\n const uploadResult = this.parseResult(result);\n options?.onComplete?.(uploadResult);\n resolveResult(uploadResult);\n });\n\n uppy.on(\"error\", (error) => {\n handleError(new Error(this.#extractErrorMessage(error)));\n });\n\n uppy.on(\"file-added\", (file) => {\n fileId = file.id;\n });\n\n return { fileId, resultPromise, progress };\n }\n\n #extractErrorMessage(error: unknown): string {\n let errorMessage = \"Upload fainormalizeDataled\";\n\n if (!error) return errorMessage;\n\n if (typeof error === \"string\") {\n errorMessage = error;\n } else if (error instanceof Error) {\n errorMessage = error.message;\n } else if ((error as any).message) {\n errorMessage = (error as any).message;\n } else if (\n (error as any).toString &&\n typeof (error as any).toString === \"function\"\n ) {\n errorMessage = (error as any).toString();\n }\n\n // Try to extract error from response (XHRUpload/TusPlugin)\n const errorObj = error as any;\n if (errorObj?.xhr?.response) {\n try {\n const response =\n typeof errorObj.xhr.response === \"string\"\n ? JSON.parse(errorObj.xhr.response)\n : errorObj.xhr.response;\n if (response.error) {\n errorMessage = response.error;\n } else if (response.message) {\n errorMessage = response.message;\n }\n } catch {\n // If parsing fails, use the original error message\n }\n }\n\n return errorMessage;\n }\n\n async #normalizeData(\n data: File | ReadableStream<Uint8Array>,\n ): Promise<File | ReadableStream<Uint8Array> | NodeStreamWithSize | Blob> {\n if (isNodeEnvironment()) {\n return this.#normalizeDataForNode(data);\n }\n return this.#normalizeDataForBrowser(data);\n }\n\n async #normalizeDataForBrowser(\n data: File | ReadableStream<Uint8Array>,\n ): Promise<File | Blob | ReadableStream<Uint8Array>> {\n // TUS plugin requires File, Blob, or Reader in browser\n if (this.getUploadSource() === UPLOAD_SOURCE_TUS) {\n if (data instanceof ReadableStream) {\n const { streamToBlobViaResponse } = await import(\"@/utils/stream\");\n return streamToBlobViaResponse(data);\n }\n return data;\n }\n\n // XHRUpload handles File/Blob directly\n return data;\n }\n\n async #normalizeDataForNode(\n data: File | ReadableStream<Uint8Array>,\n ): Promise<NodeStreamWithSize | File | Blob | ReadableStream<Uint8Array>> {\n // XHRUpload with formData: true requires Blob/File for FormData.append()\n // Do not convert to Node.js stream for XHRUpload\n if (this.getUploadSource() === UPLOAD_SOURCE_XHR) {\n return data;\n }\n\n // Convert File to ReadableStream without loading entire blob into memory\n if (data instanceof File) {\n const stream = fileToReadableStream(data);\n // Convert to Node.js stream for tus-js-client\n const nodeStream = await readableStreamToNodeStream(stream);\n return nodeStream as NodeStreamWithSize;\n }\n\n // In Node.js, convert ReadableStream to Node.js stream.Readable for tus-js-client's NodeFileReader\n if (data instanceof ReadableStream) {\n const nodeStream = await readableStreamToNodeStream(data);\n // Add size property to satisfy Uppy's type requirements\n return nodeStream as NodeStreamWithSize;\n }\n\n return data;\n }\n\n async #addFileToUppy(\n uppy: Uppy,\n normalized: {\n /**\n * List of file manager items\n */\n data: File | ReadableStream<Uint8Array>;\n /**\n * Name for the pin or filter\n */\n name: string;\n /**\n * MIME type or content type\n */\n type: string;\n },\n size?: number,\n ): Promise<void> {\n const fileData = await this.#normalizeData(normalized.data);\n const fileOptions = {\n source: this.getUploadSource(),\n name: normalized.name,\n type: normalized.type,\n data: fileData as any,\n };\n\n // Add file to Uppy first\n // Note: Uppy accepts any data type as it defers to the drivers\n const fileId = uppy.addFile(fileOptions);\n\n // Set TUS upload size if provided\n // In Node.js, streams need explicit size for tus-js-client\n // In browser, Uppy's TUS plugin may not derive size from Blob automatically\n if (\n this.getUploadSource() === UPLOAD_SOURCE_TUS &&\n size !== undefined &&\n size > 0\n ) {\n uppy.setFileState(fileId, {\n tus: { uploadSize: size },\n });\n }\n }\n\n #startUpload(uppy: Uppy, options?: UploadOptions): void {\n uppy.upload().catch((error) => {\n options?.onError?.(new Error(this.#extractErrorMessage(error)));\n });\n }\n\n #createUploadOperation(\n uppy: Uppy,\n fileId: string | null,\n resultPromise: Promise<UploadResult>,\n progress: UploadProgress,\n ): UploadOperation {\n return {\n cancel: () => {\n uppy.cancelAll();\n },\n pause: () => {\n if (fileId) {\n uppy.pauseResume(fileId);\n }\n },\n resume: () => {\n if (fileId) {\n uppy.pauseResume(fileId);\n }\n },\n result: resultPromise,\n progress: Object.freeze({ ...progress }),\n };\n }\n\n destroy(): void {\n // No-op since each upload creates its own Uppy instance\n }\n\n protected abstract configurePlugin(uppy: Uppy): void | Promise<void>;\n protected abstract parseResult(result: unknown): UploadResult;\n protected abstract getUploadSource(): string;\n\n protected mapUploadResponse(\n body: unknown,\n uploadId: string,\n overrides?: Partial<UploadResult>,\n ): UploadResult {\n if (body && typeof body === \"object\" && \"CID\" in body) {\n const { CID } = body as PostUploadResponse;\n return {\n id: uploadId,\n cid: CID,\n name: \"\",\n size: 0,\n mimeType: \"\",\n createdAt: new Date(),\n numberOfFiles: 1,\n [UploadResultSymbol]: true,\n ...overrides,\n };\n }\n\n return {\n id: uploadId,\n name: \"\",\n size: 0,\n mimeType: \"\",\n createdAt: new Date(),\n numberOfFiles: 1,\n [UploadResultSymbol]: true,\n ...overrides,\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -55,5 +55,5 @@ declare class UploadBuilderNamespace {
|
|
|
55
55
|
*/
|
|
56
56
|
type UploadMethodAndBuilder = ((file: File, options?: UploadOptions) => Promise<UploadOperation>) & UploadBuilderNamespace;
|
|
57
57
|
//#endregion
|
|
58
|
-
export { UploadMethodAndBuilder };
|
|
58
|
+
export { UploadBuilderNamespace, UploadMethodAndBuilder };
|
|
59
59
|
//# sourceMappingURL=builder.d.ts.map
|
package/dist/esm/upload/car.d.ts
CHANGED
|
@@ -2,13 +2,31 @@ import { Datastore } from "interface-datastore";
|
|
|
2
2
|
|
|
3
3
|
//#region src/upload/car.d.ts
|
|
4
4
|
interface CarPreprocessOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Name for the pin or filter
|
|
7
|
+
*/
|
|
5
8
|
name?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Progress callback for preprocessing
|
|
11
|
+
*/
|
|
6
12
|
onProgress?: (percentage: number) => void;
|
|
13
|
+
/**
|
|
14
|
+
* AbortSignal to cancel the operation
|
|
15
|
+
*/
|
|
7
16
|
signal?: AbortSignal;
|
|
8
17
|
}
|
|
9
18
|
interface CarPreprocessResult {
|
|
19
|
+
/**
|
|
20
|
+
* Preprocessed CAR data as a readable stream
|
|
21
|
+
*/
|
|
10
22
|
carStream: ReadableStream<Uint8Array>;
|
|
23
|
+
/**
|
|
24
|
+
* Root CID of the CAR file
|
|
25
|
+
*/
|
|
11
26
|
rootCid: string;
|
|
27
|
+
/**
|
|
28
|
+
* Size in bytes
|
|
29
|
+
*/
|
|
12
30
|
size: bigint;
|
|
13
31
|
}
|
|
14
32
|
declare function preprocessToCar(input: File | ReadableStream<Uint8Array> | File[], options?: CarPreprocessOptions): Promise<CarPreprocessResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"car.js","names":["createUnstorageDatastore"],"sources":["../../../src/upload/car.ts"],"sourcesContent":["import { car } from \"@helia/car\";\nimport { createHeliaHTTP } from \"@helia/http\";\nimport { unixfs } from \"@helia/unixfs\";\nimport {\n createBlockstore,\n createDatastore as createUnstorageDatastore,\n} from \"@/blockstore\";\nimport type { CID } from \"multiformats/cid\";\nimport { CarReader } from \"@ipld/car\";\nimport type { Datastore } from \"interface-datastore\";\n\nimport {\n asyncGeneratorToReadableStream,\n calculateStreamSize,\n readableStreamToAsyncIterable,\n streamToBlob,\n} from \"@/utils/stream\";\nimport { FILE_EXTENSION_CAR, MIME_TYPE_CAR } from \"@/types/mime-types\";\n\nexport interface CarPreprocessOptions {\n name?: string;\n onProgress?: (percentage: number) => void;\n signal?: AbortSignal;\n}\n\nexport interface CarPreprocessResult {\n carStream: ReadableStream<Uint8Array>;\n rootCid: string;\n size: bigint;\n}\n\nexport interface CarConfig {\n /**\n * Custom datastore instance for Helia.\n * If provided, this datastore will be used directly without creating one from storage.\n * Highest priority - takes precedence over storage and datastoreName.\n */\n datastore?: Datastore;\n\n /**\n * Custom base name for Helia storage.\n * Passed as the `base` option to both blockstore and datastore storage instances.\n * Only used when datastore is not provided.\n * @default \"pinner-helia-data\"\n */\n datastoreName?: string;\n}\n\nlet helia: any = null;\nlet blockstore: any = null;\nlet datastore: any = null;\nlet config: CarConfig = {};\n\nexport function configureCar(carConfig: CarConfig) {\n config = carConfig;\n}\n\nasync function getHelia() {\n if (helia) return helia;\n\n const BlockstoreClass = createBlockstore();\n const DatastoreClass = createUnstorageDatastore();\n\n blockstore = new BlockstoreClass({\n prefix: \"pinner-helia-blocks\",\n base: config.datastoreName,\n });\n datastore =\n config.datastore ||\n new DatastoreClass({\n prefix: \"pinner-helia-data\",\n base: config.datastoreName,\n });\n\n helia = await createHeliaHTTP({\n blockstore,\n datastore,\n });\n\n return helia;\n}\n\nasync function cleanupHelia() {\n if (datastore?.close) {\n await datastore.close();\n }\n helia = null;\n blockstore = null;\n datastore = null;\n}\n\nasync function* fileSource(\n files: File[],\n onProgress?: (percentage: number) => void,\n signal?: AbortSignal,\n): AsyncGenerator<{\n content: AsyncIterable<Uint8Array> | undefined;\n path: string;\n}> {\n const seenDirs = new Set<string>();\n let totalBytes = 0n;\n let processedBytes = 0n;\n\n for (const file of files) {\n totalBytes += BigInt(file.size);\n }\n\n for (const file of files) {\n if (signal?.aborted) {\n throw new Error(\"Aborted\");\n }\n\n const fullPath = (file as any).webkitRelativePath ?? file.name;\n\n if (fullPath.includes(\"/.\")) {\n continue;\n }\n\n const parts = fullPath.split(\"/\").filter((part: string) => part.length > 0);\n\n for (let i = 1; i < parts.length; i++) {\n if (signal?.aborted) {\n throw new Error(\"Aborted\");\n }\n\n const dirPath = parts.slice(0, i).join(\"/\");\n\n if (!seenDirs.has(dirPath)) {\n seenDirs.add(dirPath);\n yield {\n content: (async function* () {})(),\n path: dirPath,\n };\n }\n }\n\n yield {\n content: readableStreamToAsyncIterable(file.stream()),\n path: fullPath,\n };\n\n if (onProgress && totalBytes > 0n) {\n processedBytes += BigInt(file.size);\n const progressPercent = Number((processedBytes * 100n) / totalBytes);\n onProgress(progressPercent);\n }\n }\n}\n\nexport async function preprocessToCar(\n input: File | ReadableStream<Uint8Array> | File[],\n options?: CarPreprocessOptions,\n): Promise<CarPreprocessResult> {\n let files: File[];\n\n if (input instanceof ReadableStream) {\n const [streamForSize, streamForFile] = input.tee();\n const size = await calculateStreamSize(streamForSize, options?.signal);\n const streamBlob = await streamToBlob(\n streamForFile,\n \"application/octet-stream\",\n );\n files = [\n new File([streamBlob], options?.name || \"upload\", {\n type: streamBlob.type,\n }),\n ];\n } else if (Array.isArray(input)) {\n files = input;\n } else {\n files = [input];\n }\n\n // wrapWithDirectory must be true for directory uploads (File[] input) so the\n // last addAll yield is the root directory CID, not a child file/subdirectory.\n // Single files don't need wrapping. Matches Go SDK wrapInDir behavior.\n const wrapWithDirectory = Array.isArray(input);\n\n const heliaInstance = await getHelia();\n const fs = unixfs(heliaInstance);\n const c = car(heliaInstance);\n\n let rootCid: CID | undefined;\n let blocksCount = 0n;\n\n const src = fileSource(files, options?.onProgress, options?.signal);\n\n let hasFiles = false;\n for await (const result of fs.addAll(src, {\n cidVersion: 1,\n rawLeaves: false,\n wrapWithDirectory,\n signal: options?.signal,\n onProgress(event) {\n if (event.type === \"blocks:put:blockstore:put\") {\n blocksCount++;\n }\n },\n })) {\n if (options?.signal?.aborted) {\n throw new Error(\"Aborted\");\n }\n rootCid = result.cid;\n hasFiles = true;\n }\n\n if (!hasFiles || !rootCid) {\n throw new Error(\"No files to process\");\n }\n\n // c.export() now returns an async generator directly (was renamed from 'stream')\n const carAsyncGenerator = c.export(rootCid!, { signal: options?.signal });\n const carStream = asyncGeneratorToReadableStream(carAsyncGenerator);\n\n // Use stream tee to create two identical streams - one for size calculation, one for processing\n const [streamForSize, streamForProcessing] = carStream.tee();\n\n const size = await calculateStreamSize(streamForSize, options?.signal);\n\n return {\n carStream: streamForProcessing,\n rootCid: rootCid!.toString(),\n size,\n };\n}\n\nexport async function isCarFile(file: File): Promise<boolean> {\n if (file.type !== MIME_TYPE_CAR && !file.name.endsWith(FILE_EXTENSION_CAR)) {\n return false;\n }\n\n try {\n const iterable = readableStreamToAsyncIterable(file.stream());\n const reader = await CarReader.fromIterable(iterable);\n const roots = await reader.getRoots();\n return roots.length > 0;\n } catch {\n return false;\n }\n}\n\nexport async function destroyCarPreprocessor() {\n await cleanupHelia();\n}\n"],"mappings":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"car.js","names":["createUnstorageDatastore"],"sources":["../../../src/upload/car.ts"],"sourcesContent":["import { car } from \"@helia/car\";\nimport { createHeliaHTTP } from \"@helia/http\";\nimport { unixfs } from \"@helia/unixfs\";\nimport {\n createBlockstore,\n createDatastore as createUnstorageDatastore,\n} from \"@/blockstore\";\nimport type { CID } from \"multiformats/cid\";\nimport { CarReader } from \"@ipld/car\";\nimport type { Datastore } from \"interface-datastore\";\n\nimport {\n asyncGeneratorToReadableStream,\n calculateStreamSize,\n readableStreamToAsyncIterable,\n streamToBlob,\n} from \"@/utils/stream\";\nimport { FILE_EXTENSION_CAR, MIME_TYPE_CAR } from \"@/types/mime-types\";\n\nexport interface CarPreprocessOptions {\n /**\n * Name for the pin or filter\n */\n name?: string;\n /**\n * Progress callback for preprocessing\n */\n onProgress?: (percentage: number) => void;\n /**\n * AbortSignal to cancel the operation\n */\n signal?: AbortSignal;\n}\n\nexport interface CarPreprocessResult {\n /**\n * Preprocessed CAR data as a readable stream\n */\n carStream: ReadableStream<Uint8Array>;\n /**\n * Root CID of the CAR file\n */\n rootCid: string;\n /**\n * Size in bytes\n */\n size: bigint;\n}\n\nexport interface CarConfig {\n /**\n * Custom datastore instance for Helia.\n * If provided, this datastore will be used directly without creating one from storage.\n * Highest priority - takes precedence over storage and datastoreName.\n */\n datastore?: Datastore;\n\n /**\n * Custom base name for Helia storage.\n * Passed as the `base` option to both blockstore and datastore storage instances.\n * Only used when datastore is not provided.\n * @default \"pinner-helia-data\"\n */\n datastoreName?: string;\n}\n\nlet helia: any = null;\nlet blockstore: any = null;\nlet datastore: any = null;\nlet config: CarConfig = {};\n\nexport function configureCar(carConfig: CarConfig) {\n config = carConfig;\n}\n\nasync function getHelia() {\n if (helia) return helia;\n\n const BlockstoreClass = createBlockstore();\n const DatastoreClass = createUnstorageDatastore();\n\n blockstore = new BlockstoreClass({\n prefix: \"pinner-helia-blocks\",\n base: config.datastoreName,\n });\n datastore =\n config.datastore ||\n new DatastoreClass({\n prefix: \"pinner-helia-data\",\n base: config.datastoreName,\n });\n\n helia = await createHeliaHTTP({\n blockstore,\n datastore,\n });\n\n return helia;\n}\n\nasync function cleanupHelia() {\n if (datastore?.close) {\n await datastore.close();\n }\n helia = null;\n blockstore = null;\n datastore = null;\n}\n\nasync function* fileSource(\n files: File[],\n onProgress?: (percentage: number) => void,\n signal?: AbortSignal,\n): AsyncGenerator<{\n content: AsyncIterable<Uint8Array> | undefined;\n /**\n * File path within the pin\n */\n path: string;\n}> {\n const seenDirs = new Set<string>();\n let totalBytes = 0n;\n let processedBytes = 0n;\n\n for (const file of files) {\n totalBytes += BigInt(file.size);\n }\n\n for (const file of files) {\n if (signal?.aborted) {\n throw new Error(\"Aborted\");\n }\n\n const fullPath = (file as any).webkitRelativePath ?? file.name;\n\n if (fullPath.includes(\"/.\")) {\n continue;\n }\n\n const parts = fullPath.split(\"/\").filter((part: string) => part.length > 0);\n\n for (let i = 1; i < parts.length; i++) {\n if (signal?.aborted) {\n throw new Error(\"Aborted\");\n }\n\n const dirPath = parts.slice(0, i).join(\"/\");\n\n if (!seenDirs.has(dirPath)) {\n seenDirs.add(dirPath);\n yield {\n content: (async function* () {})(),\n path: dirPath,\n };\n }\n }\n\n yield {\n content: readableStreamToAsyncIterable(file.stream()),\n path: fullPath,\n };\n\n if (onProgress && totalBytes > 0n) {\n processedBytes += BigInt(file.size);\n const progressPercent = Number((processedBytes * 100n) / totalBytes);\n onProgress(progressPercent);\n }\n }\n}\n\nexport async function preprocessToCar(\n input: File | ReadableStream<Uint8Array> | File[],\n options?: CarPreprocessOptions,\n): Promise<CarPreprocessResult> {\n let files: File[];\n\n if (input instanceof ReadableStream) {\n const [streamForSize, streamForFile] = input.tee();\n const size = await calculateStreamSize(streamForSize, options?.signal);\n const streamBlob = await streamToBlob(\n streamForFile,\n \"application/octet-stream\",\n );\n files = [\n new File([streamBlob], options?.name || \"upload\", {\n type: streamBlob.type,\n }),\n ];\n } else if (Array.isArray(input)) {\n files = input;\n } else {\n files = [input];\n }\n\n // wrapWithDirectory must be true for directory uploads (File[] input) so the\n // last addAll yield is the root directory CID, not a child file/subdirectory.\n // Single files don't need wrapping. Matches Go SDK wrapInDir behavior.\n const wrapWithDirectory = Array.isArray(input);\n\n const heliaInstance = await getHelia();\n const fs = unixfs(heliaInstance);\n const c = car(heliaInstance);\n\n let rootCid: CID | undefined;\n let blocksCount = 0n;\n\n const src = fileSource(files, options?.onProgress, options?.signal);\n\n let hasFiles = false;\n for await (const result of fs.addAll(src, {\n cidVersion: 1,\n rawLeaves: false,\n wrapWithDirectory,\n signal: options?.signal,\n onProgress(event) {\n if (event.type === \"blocks:put:blockstore:put\") {\n blocksCount++;\n }\n },\n })) {\n if (options?.signal?.aborted) {\n throw new Error(\"Aborted\");\n }\n rootCid = result.cid;\n hasFiles = true;\n }\n\n if (!hasFiles || !rootCid) {\n throw new Error(\"No files to process\");\n }\n\n // c.export() now returns an async generator directly (was renamed from 'stream')\n const carAsyncGenerator = c.export(rootCid!, { signal: options?.signal });\n const carStream = asyncGeneratorToReadableStream(carAsyncGenerator);\n\n // Use stream tee to create two identical streams - one for size calculation, one for processing\n const [streamForSize, streamForProcessing] = carStream.tee();\n\n const size = await calculateStreamSize(streamForSize, options?.signal);\n\n return {\n carStream: streamForProcessing,\n rootCid: rootCid!.toString(),\n size,\n };\n}\n\nexport async function isCarFile(file: File): Promise<boolean> {\n if (file.type !== MIME_TYPE_CAR && !file.name.endsWith(FILE_EXTENSION_CAR)) {\n return false;\n }\n\n try {\n const iterable = readableStreamToAsyncIterable(file.stream());\n const reader = await CarReader.fromIterable(iterable);\n const roots = await reader.getRoots();\n return roots.length > 0;\n } catch {\n return false;\n }\n}\n\nexport async function destroyCarPreprocessor() {\n await cleanupHelia();\n}\n"],"mappings":"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"}
|