@xgsd/artifact-sdk 0.0.1

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.
@@ -0,0 +1,30 @@
1
+ type Ref = {
2
+ group: string;
3
+ key: string;
4
+ method: "get" | "put";
5
+ contentType?: string;
6
+ };
7
+ declare class ArtifactStorage {
8
+ private readonly url;
9
+ private readonly accessToken?;
10
+ constructor(url: string, accessToken?: string | undefined);
11
+ protected getSignedUrl(ref: Ref): Promise<{
12
+ url: string;
13
+ method: string;
14
+ }>;
15
+ uploadJson(path: string, obj: Record<string, unknown>): Promise<{
16
+ ok: true;
17
+ }>;
18
+ uploadFile(localPath: string, remotePath: string, contentType?: string): Promise<{
19
+ ok: true;
20
+ }>;
21
+ downloadJson<T = Record<string, unknown>>(path: string): Promise<T>;
22
+ downloadFile(remotePath: string, localPath: string): Promise<void>;
23
+ exists(path: string): Promise<boolean>;
24
+ delete(path: string): Promise<{
25
+ ok: true;
26
+ }>;
27
+ list(prefix: string): Promise<string[]>;
28
+ }
29
+
30
+ export { ArtifactStorage };
package/dist/index.js ADDED
@@ -0,0 +1,216 @@
1
+ // src/index.ts
2
+ import { createReadStream, createWriteStream } from "fs";
3
+ import { pipeline } from "stream/promises";
4
+ function parse(q) {
5
+ const idx = q.indexOf(":");
6
+ if (idx === -1) {
7
+ return null;
8
+ }
9
+ const group = q.slice(0, idx);
10
+ const key = q.slice(idx + 1);
11
+ if (!group || !key) {
12
+ return null;
13
+ }
14
+ return {
15
+ group,
16
+ key
17
+ };
18
+ }
19
+ var ArtifactStorage = class {
20
+ constructor(url, accessToken) {
21
+ this.url = url;
22
+ this.accessToken = accessToken;
23
+ }
24
+ url;
25
+ accessToken;
26
+ async getSignedUrl(ref) {
27
+ const url = `${this.url}/sign`;
28
+ const res = await fetch(url, {
29
+ method: "POST",
30
+ body: JSON.stringify(ref),
31
+ headers: {
32
+ authorization: `Bearer ${this.accessToken}`,
33
+ "content-type": "application/json",
34
+ accept: "application/json"
35
+ }
36
+ });
37
+ if (res.status >= 400) {
38
+ throw new Error(`http-${res.status}`);
39
+ }
40
+ const json = await res.json();
41
+ return {
42
+ url: json.url,
43
+ method: json.method
44
+ };
45
+ }
46
+ // -----------------------
47
+ // JSON UPLOAD
48
+ // -----------------------
49
+ async uploadJson(path, obj) {
50
+ const ref = parse(path);
51
+ if (!ref) {
52
+ throw new Error("invalid remote path");
53
+ }
54
+ const { url, method } = await this.getSignedUrl({
55
+ ...ref,
56
+ method: "put",
57
+ contentType: "application/json"
58
+ });
59
+ const res = await fetch(url, {
60
+ method,
61
+ body: JSON.stringify(obj),
62
+ headers: {
63
+ "content-type": "application/json"
64
+ }
65
+ });
66
+ if (res.status >= 400) {
67
+ throw new Error(`http-${res.status}`);
68
+ }
69
+ return { ok: true };
70
+ }
71
+ // -----------------------
72
+ // FILE UPLOAD
73
+ // -----------------------
74
+ async uploadFile(localPath, remotePath, contentType = "application/octet-stream") {
75
+ const ref = parse(remotePath);
76
+ if (!ref) {
77
+ throw new Error("invalid remote path");
78
+ }
79
+ const { url, method } = await this.getSignedUrl({
80
+ ...ref,
81
+ method: "put",
82
+ contentType
83
+ });
84
+ const stream = createReadStream(localPath);
85
+ const res = await fetch(url, {
86
+ method,
87
+ body: stream,
88
+ //duplex: 'half',
89
+ headers: {
90
+ "content-type": contentType
91
+ }
92
+ });
93
+ if (res.status >= 400) {
94
+ throw new Error(`http-${res.status}`);
95
+ }
96
+ return { ok: true };
97
+ }
98
+ // -----------------------
99
+ // JSON DOWNLOAD
100
+ // -----------------------
101
+ async downloadJson(path) {
102
+ const ref = parse(path);
103
+ if (!ref) {
104
+ throw new Error("invalid remote path");
105
+ }
106
+ const { url, method } = await this.getSignedUrl({
107
+ ...ref,
108
+ method: "get"
109
+ });
110
+ const res = await fetch(url, {
111
+ method
112
+ });
113
+ if (res.status >= 400) {
114
+ throw new Error(`http-${res.status}`);
115
+ }
116
+ return await res.json();
117
+ }
118
+ // -----------------------
119
+ // FILE DOWNLOAD
120
+ // -----------------------
121
+ async downloadFile(remotePath, localPath) {
122
+ const ref = parse(remotePath);
123
+ if (!ref) {
124
+ throw new Error("invalid remote path");
125
+ }
126
+ const { url, method } = await this.getSignedUrl({
127
+ ...ref,
128
+ method: "get"
129
+ });
130
+ const res = await fetch(url, {
131
+ method
132
+ });
133
+ if (res.status >= 400) {
134
+ throw new Error(`http-${res.status}`);
135
+ }
136
+ if (!res.body) {
137
+ throw new Error("missing response body");
138
+ }
139
+ const file = createWriteStream(localPath);
140
+ await pipeline(res.body, file);
141
+ }
142
+ // -----------------------
143
+ // EXISTS
144
+ // -----------------------
145
+ async exists(path) {
146
+ try {
147
+ const ref = parse(path);
148
+ if (!ref) {
149
+ throw new Error("invalid remote path");
150
+ }
151
+ const { url } = await this.getSignedUrl({
152
+ ...ref,
153
+ method: "get"
154
+ });
155
+ const res = await fetch(url, {
156
+ method: "HEAD"
157
+ });
158
+ return res.status < 400;
159
+ } catch {
160
+ return false;
161
+ }
162
+ }
163
+ // -----------------------
164
+ // DELETE
165
+ // -----------------------
166
+ async delete(path) {
167
+ const ref = parse(path);
168
+ if (!ref) {
169
+ throw new Error("invalid remote path");
170
+ }
171
+ const url = `${this.url}/delete`;
172
+ const res = await fetch(url, {
173
+ method: "POST",
174
+ body: JSON.stringify(ref),
175
+ headers: {
176
+ authorization: `Bearer ${this.accessToken}`,
177
+ "content-type": "application/json",
178
+ accept: "application/json"
179
+ }
180
+ });
181
+ if (res.status >= 400) {
182
+ throw new Error(`http-${res.status}`);
183
+ }
184
+ return { ok: true };
185
+ }
186
+ // -----------------------
187
+ // LIST
188
+ // -----------------------
189
+ async list(prefix) {
190
+ const ref = parse(prefix);
191
+ if (!ref) {
192
+ throw new Error("invalid remote path");
193
+ }
194
+ const url = `${this.url}/list`;
195
+ const res = await fetch(url, {
196
+ method: "POST",
197
+ body: JSON.stringify({
198
+ group: ref.group,
199
+ prefix: ref.key
200
+ }),
201
+ headers: {
202
+ authorization: `Bearer ${this.accessToken}`,
203
+ "content-type": "application/json",
204
+ accept: "application/json"
205
+ }
206
+ });
207
+ if (res.status >= 400) {
208
+ throw new Error(`http-${res.status}`);
209
+ }
210
+ return await res.json();
211
+ }
212
+ };
213
+ export {
214
+ ArtifactStorage
215
+ };
216
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { createReadStream, createWriteStream } from \"node:fs\";\nimport { pipeline } from \"node:stream/promises\";\n\ntype Ref = {\n group: string;\n key: string;\n method: \"get\" | \"put\";\n contentType?: string;\n};\n\nfunction parse(q: string): {\n group: string;\n key: string;\n} | null {\n const idx = q.indexOf(\":\");\n\n if (idx === -1) {\n return null;\n }\n\n const group = q.slice(0, idx);\n const key = q.slice(idx + 1);\n\n if (!group || !key) {\n return null;\n }\n\n return {\n group,\n key,\n };\n}\n\nexport class ArtifactStorage {\n constructor(\n private readonly url: string,\n private readonly accessToken?: string,\n ) {}\n\n protected async getSignedUrl(ref: Ref): Promise<{\n url: string;\n method: string;\n }> {\n const url = `${this.url}/sign`;\n\n const res = await fetch(url, {\n method: \"POST\",\n body: JSON.stringify(ref),\n headers: {\n authorization: `Bearer ${this.accessToken}`,\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n },\n });\n\n if (res.status >= 400) {\n throw new Error(`http-${res.status}`);\n }\n\n const json = await res.json();\n\n return {\n url: json.url,\n method: json.method,\n };\n }\n\n // -----------------------\n // JSON UPLOAD\n // -----------------------\n\n async uploadJson(\n path: string,\n obj: Record<string, unknown>,\n ): Promise<{ ok: true }> {\n const ref = parse(path);\n\n if (!ref) {\n throw new Error(\"invalid remote path\");\n }\n\n const { url, method } = await this.getSignedUrl({\n ...ref,\n method: \"put\",\n contentType: \"application/json\",\n });\n\n const res = await fetch(url, {\n method,\n body: JSON.stringify(obj),\n headers: {\n \"content-type\": \"application/json\",\n },\n });\n\n if (res.status >= 400) {\n throw new Error(`http-${res.status}`);\n }\n\n return { ok: true };\n }\n\n // -----------------------\n // FILE UPLOAD\n // -----------------------\n\n async uploadFile(\n localPath: string,\n remotePath: string,\n contentType = \"application/octet-stream\",\n ): Promise<{ ok: true }> {\n const ref = parse(remotePath);\n\n if (!ref) {\n throw new Error(\"invalid remote path\");\n }\n\n const { url, method } = await this.getSignedUrl({\n ...ref,\n method: \"put\",\n contentType,\n });\n\n const stream = createReadStream(localPath);\n\n const res = await fetch(url, {\n method,\n body: stream as any,\n //duplex: 'half',\n headers: {\n \"content-type\": contentType,\n },\n });\n\n if (res.status >= 400) {\n throw new Error(`http-${res.status}`);\n }\n\n return { ok: true };\n }\n\n // -----------------------\n // JSON DOWNLOAD\n // -----------------------\n\n async downloadJson<T = Record<string, unknown>>(path: string): Promise<T> {\n const ref = parse(path);\n\n if (!ref) {\n throw new Error(\"invalid remote path\");\n }\n\n const { url, method } = await this.getSignedUrl({\n ...ref,\n method: \"get\",\n });\n\n const res = await fetch(url, {\n method,\n });\n\n if (res.status >= 400) {\n throw new Error(`http-${res.status}`);\n }\n\n return (await res.json()) as T;\n }\n\n // -----------------------\n // FILE DOWNLOAD\n // -----------------------\n\n async downloadFile(remotePath: string, localPath: string): Promise<void> {\n const ref = parse(remotePath);\n\n if (!ref) {\n throw new Error(\"invalid remote path\");\n }\n\n const { url, method } = await this.getSignedUrl({\n ...ref,\n method: \"get\",\n });\n\n const res = await fetch(url, {\n method,\n });\n\n if (res.status >= 400) {\n throw new Error(`http-${res.status}`);\n }\n\n if (!res.body) {\n throw new Error(\"missing response body\");\n }\n\n const file = createWriteStream(localPath);\n\n await pipeline(res.body as any, file);\n }\n\n // -----------------------\n // EXISTS\n // -----------------------\n\n async exists(path: string): Promise<boolean> {\n try {\n const ref = parse(path);\n\n if (!ref) {\n throw new Error(\"invalid remote path\");\n }\n\n const { url } = await this.getSignedUrl({\n ...ref,\n method: \"get\",\n });\n\n const res = await fetch(url, {\n method: \"HEAD\",\n });\n\n return res.status < 400;\n } catch {\n return false;\n }\n }\n\n // -----------------------\n // DELETE\n // -----------------------\n\n async delete(path: string): Promise<{ ok: true }> {\n const ref = parse(path);\n\n if (!ref) {\n throw new Error(\"invalid remote path\");\n }\n\n const url = `${this.url}/delete`;\n\n const res = await fetch(url, {\n method: \"POST\",\n body: JSON.stringify(ref),\n headers: {\n authorization: `Bearer ${this.accessToken}`,\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n },\n });\n\n if (res.status >= 400) {\n throw new Error(`http-${res.status}`);\n }\n\n return { ok: true };\n }\n\n // -----------------------\n // LIST\n // -----------------------\n\n async list(prefix: string): Promise<string[]> {\n const ref = parse(prefix);\n\n if (!ref) {\n throw new Error(\"invalid remote path\");\n }\n\n const url = `${this.url}/list`;\n\n const res = await fetch(url, {\n method: \"POST\",\n body: JSON.stringify({\n group: ref.group,\n prefix: ref.key,\n }),\n headers: {\n authorization: `Bearer ${this.accessToken}`,\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n },\n });\n\n if (res.status >= 400) {\n throw new Error(`http-${res.status}`);\n }\n\n return await res.json();\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@xgsd/artifact-sdk",
3
+ "version": "0.0.1",
4
+ "main": "dist/index.js",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "scripts": {
8
+ "build": "tsup"
9
+ },
10
+ "devDependencies": {
11
+ "@types/node": "^25.9.1",
12
+ "tsup": "^8.5.1",
13
+ "typescript": "^5"
14
+ }
15
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig({
4
+ entry: {
5
+ index: "src/index.ts",
6
+ },
7
+ format: ["esm"],
8
+ dts: true,
9
+ clean: true,
10
+ sourcemap: true,
11
+ target: "es2022",
12
+ });