kassza 0.1.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/LICENSE +21 -0
- package/README.md +150 -0
- package/agents/README.md +59 -0
- package/agents/api.md +206 -0
- package/agents/pitfalls.md +52 -0
- package/agents/recipes.md +217 -0
- package/agents/skills/kassza/SKILL.md +29 -0
- package/assets/logo-wordmark.svg +25 -0
- package/assets/logo.svg +19 -0
- package/dist/binary-B89HM03_.cjs +63 -0
- package/dist/binary-D0M9U2MD.js +34 -0
- package/dist/client-B9kbKKBf.d.cts +748 -0
- package/dist/client-zv4enyq7.d.ts +748 -0
- package/dist/cookie-stores/index.cjs +153 -0
- package/dist/cookie-stores/index.d.cts +63 -0
- package/dist/cookie-stores/index.d.ts +63 -0
- package/dist/cookie-stores/index.js +144 -0
- package/dist/create-BZd6CUO7.cjs +1160 -0
- package/dist/create-DDZaNlOz.js +939 -0
- package/dist/dates-D2XebOIg.js +34 -0
- package/dist/dates-DtHzwNU6.d.cts +7 -0
- package/dist/dates-DtHzwNU6.d.ts +7 -0
- package/dist/dates-PPxH0n5H.cjs +57 -0
- package/dist/errors-B0QJhUW1.cjs +302 -0
- package/dist/errors-DapdV5DK.js +273 -0
- package/dist/index-CYAGCdKJ.d.cts +56 -0
- package/dist/index-CYAGCdKJ.d.ts +56 -0
- package/dist/index.cjs +1406 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +1394 -0
- package/dist/ipn/index.cjs +108 -0
- package/dist/ipn/index.d.cts +32 -0
- package/dist/ipn/index.d.ts +32 -0
- package/dist/ipn/index.js +101 -0
- package/dist/money/index.cjs +15 -0
- package/dist/money/index.d.cts +2 -0
- package/dist/money/index.d.ts +2 -0
- package/dist/money/index.js +2 -0
- package/dist/money-C9j7nBNP.js +258 -0
- package/dist/money-DkiGukAw.cjs +335 -0
- package/dist/session-CRGOuz-R.cjs +100 -0
- package/dist/session-Dm_45x8K.d.cts +11 -0
- package/dist/session-Dm_45x8K.d.ts +11 -0
- package/dist/session-OJMLGufT.js +77 -0
- package/dist/shared-CrxlxI8n.cjs +207 -0
- package/dist/shared-D6DLa4b7.js +100 -0
- package/dist/storage/fs.cjs +88 -0
- package/dist/storage/fs.d.cts +13 -0
- package/dist/storage/fs.d.ts +13 -0
- package/dist/storage/fs.js +87 -0
- package/dist/storage/index.cjs +643 -0
- package/dist/storage/index.d.cts +288 -0
- package/dist/storage/index.d.ts +288 -0
- package/dist/storage/index.js +619 -0
- package/dist/testing/index.cjs +409 -0
- package/dist/testing/index.d.cts +35 -0
- package/dist/testing/index.d.ts +35 -0
- package/dist/testing/index.js +407 -0
- package/dist/types-DVRgwcqg.d.cts +26 -0
- package/dist/types-DVRgwcqg.d.ts +26 -0
- package/dist/validators/index.cjs +296 -0
- package/dist/validators/index.d.cts +51 -0
- package/dist/validators/index.d.ts +51 -0
- package/dist/validators/index.js +278 -0
- package/llms.txt +16 -0
- package/package.json +151 -0
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
import { n as SzamlazzError } from "../errors-DapdV5DK.js";
|
|
2
|
+
import { a as isPdf } from "../binary-D0M9U2MD.js";
|
|
3
|
+
import { i as todayInBudapest, n as toAgentDate } from "../dates-D2XebOIg.js";
|
|
4
|
+
import { _ as guardStorageCall, a as assertStorageKey, c as encodeRfc3986, d as normalizePrefix, f as resolveKey, g as describeError, h as StorageError, i as assertExpires, l as joinPublicUrl, m as unsupportedSignedUrl, n as PDF_CONTENT_TYPE, o as basename, p as toArrayBuffer, r as S3_MAX_PRESIGN_SECONDS, s as encodeKeyPath, u as missingPublicBaseUrl, v as isStorageError } from "../shared-D6DLa4b7.js";
|
|
5
|
+
//#region src/storage/keys.ts
|
|
6
|
+
const DEFAULT_INVOICE_KEY_PREFIX = "szamlak";
|
|
7
|
+
const DEFAULT_RECEIPT_KEY_PREFIX = "nyugtak";
|
|
8
|
+
const KIND_FOLDERS = {
|
|
9
|
+
invoice: void 0,
|
|
10
|
+
proforma: "dijbekero",
|
|
11
|
+
advance: "elolegszamla",
|
|
12
|
+
final: "vegszamla",
|
|
13
|
+
corrective: "helyesbito",
|
|
14
|
+
storno: "sztorno",
|
|
15
|
+
deliveryNote: "szallitolevel"
|
|
16
|
+
};
|
|
17
|
+
const COMBINING_MARKS = /\p{M}/gu;
|
|
18
|
+
function sanitizeKeySegment(value) {
|
|
19
|
+
return value.normalize("NFKD").replace(COMBINING_MARKS, "").replace(/[^A-Za-z0-9._-]+/g, "-").replace(/-{2,}/g, "-").replace(/^[-.]+|[-.]+$/g, "");
|
|
20
|
+
}
|
|
21
|
+
function datedPdfKey(prefix, folder, number, date) {
|
|
22
|
+
const fileName = sanitizeKeySegment(number);
|
|
23
|
+
if (fileName === "") throw new StorageError(`A bizonylatszámból nem képezhető fájlnév: ${JSON.stringify(number)}`, { operation: "key" });
|
|
24
|
+
const [year = "", month = ""] = (date === void 0 ? todayInBudapest() : toAgentDate(date)).split("-");
|
|
25
|
+
return [
|
|
26
|
+
...prefix.split("/"),
|
|
27
|
+
folder ?? "",
|
|
28
|
+
year,
|
|
29
|
+
month
|
|
30
|
+
].map(sanitizeKeySegment).filter((segment) => segment !== "").concat(`${fileName}.pdf`).join("/");
|
|
31
|
+
}
|
|
32
|
+
function invoicePdfKey(input) {
|
|
33
|
+
return datedPdfKey(input.prefix ?? "szamlak", KIND_FOLDERS[input.type ?? "invoice"], input.number, input.date);
|
|
34
|
+
}
|
|
35
|
+
function receiptPdfKey(input) {
|
|
36
|
+
return datedPdfKey(input.prefix ?? "nyugtak", void 0, input.number, input.date);
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/storage/memory.ts
|
|
40
|
+
const MEMORY_BASE_URL = "memory://storage";
|
|
41
|
+
function memoryStorage(options = {}) {
|
|
42
|
+
const prefix = normalizePrefix(options.prefix);
|
|
43
|
+
const files = /* @__PURE__ */ new Map();
|
|
44
|
+
const urlFor = (fullKey) => joinPublicUrl(options.publicBaseUrl ?? MEMORY_BASE_URL, fullKey);
|
|
45
|
+
return {
|
|
46
|
+
files,
|
|
47
|
+
async put(key, body, putOptions) {
|
|
48
|
+
const fullKey = resolveKey(prefix, key);
|
|
49
|
+
files.set(fullKey, {
|
|
50
|
+
body: body.slice(),
|
|
51
|
+
contentType: putOptions.contentType
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
key,
|
|
55
|
+
url: urlFor(fullKey),
|
|
56
|
+
size: body.byteLength,
|
|
57
|
+
contentType: putOptions.contentType
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
async get(key) {
|
|
61
|
+
return files.get(resolveKey(prefix, key))?.body.slice();
|
|
62
|
+
},
|
|
63
|
+
async delete(key) {
|
|
64
|
+
files.delete(resolveKey(prefix, key));
|
|
65
|
+
},
|
|
66
|
+
async getUrl(key) {
|
|
67
|
+
const fullKey = resolveKey(prefix, key);
|
|
68
|
+
if (!files.has(fullKey)) throw new StorageError(`Nincs ilyen fájl: ${key}`, {
|
|
69
|
+
operation: "getUrl",
|
|
70
|
+
key,
|
|
71
|
+
status: 404
|
|
72
|
+
});
|
|
73
|
+
return urlFor(fullKey);
|
|
74
|
+
},
|
|
75
|
+
clear() {
|
|
76
|
+
files.clear();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/storage/r2-binding.ts
|
|
82
|
+
function r2BindingStorage(bucket, options = {}) {
|
|
83
|
+
const prefix = normalizePrefix(options.prefix);
|
|
84
|
+
return {
|
|
85
|
+
async put(key, body, putOptions) {
|
|
86
|
+
const fullKey = resolveKey(prefix, key);
|
|
87
|
+
const object = await guardStorageCall("R2", "put", key, () => bucket.put(fullKey, body, { httpMetadata: { contentType: putOptions.contentType } }));
|
|
88
|
+
if (!object) throw new StorageError("Az R2 nem mentette el a fájlt (a put null-t adott vissza).", {
|
|
89
|
+
operation: "put",
|
|
90
|
+
key
|
|
91
|
+
});
|
|
92
|
+
return {
|
|
93
|
+
key,
|
|
94
|
+
url: options.publicBaseUrl ? joinPublicUrl(options.publicBaseUrl, fullKey) : void 0,
|
|
95
|
+
size: object.size,
|
|
96
|
+
contentType: putOptions.contentType
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
async get(key) {
|
|
100
|
+
const fullKey = resolveKey(prefix, key);
|
|
101
|
+
const object = await guardStorageCall("R2", "get", key, () => bucket.get(fullKey));
|
|
102
|
+
if (!object) return void 0;
|
|
103
|
+
return new Uint8Array(await guardStorageCall("R2", "get", key, () => object.arrayBuffer()));
|
|
104
|
+
},
|
|
105
|
+
async delete(key) {
|
|
106
|
+
const fullKey = resolveKey(prefix, key);
|
|
107
|
+
await guardStorageCall("R2", "delete", key, () => bucket.delete(fullKey));
|
|
108
|
+
},
|
|
109
|
+
async getUrl(key, urlOptions = {}) {
|
|
110
|
+
const fullKey = resolveKey(prefix, key);
|
|
111
|
+
if (urlOptions.expiresInSeconds !== void 0) throw unsupportedSignedUrl("r2BindingStorage", key);
|
|
112
|
+
if (!options.publicBaseUrl) throw missingPublicBaseUrl("r2BindingStorage", key);
|
|
113
|
+
return joinPublicUrl(options.publicBaseUrl, fullKey);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/storage/s3.ts
|
|
119
|
+
function statusOf(error) {
|
|
120
|
+
const status = error?.$metadata?.httpStatusCode;
|
|
121
|
+
return typeof status === "number" ? status : void 0;
|
|
122
|
+
}
|
|
123
|
+
function isNotFound$1(error) {
|
|
124
|
+
const name = error?.name;
|
|
125
|
+
return name === "NoSuchKey" || name === "NotFound" || statusOf(error) === 404;
|
|
126
|
+
}
|
|
127
|
+
function wrap(operation, key, error) {
|
|
128
|
+
return new StorageError(`S3 hiba (${operation}): ${describeError(error)}`, {
|
|
129
|
+
operation,
|
|
130
|
+
key,
|
|
131
|
+
status: statusOf(error),
|
|
132
|
+
cause: error
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function missingCommand(name, key, operation) {
|
|
136
|
+
return new StorageError(`Ehhez a művelethez add meg a commands.${name} osztályt.`, {
|
|
137
|
+
operation,
|
|
138
|
+
key
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
async function readBody(output) {
|
|
142
|
+
const body = output?.Body;
|
|
143
|
+
if (!body || typeof body.transformToByteArray !== "function") throw new Error("A GetObject válasza nem tartalmaz olvasható Body-t.");
|
|
144
|
+
return await body.transformToByteArray();
|
|
145
|
+
}
|
|
146
|
+
function s3Storage(options) {
|
|
147
|
+
const prefix = normalizePrefix(options.prefix);
|
|
148
|
+
const { client, bucket, commands } = options;
|
|
149
|
+
return {
|
|
150
|
+
async put(key, body, putOptions) {
|
|
151
|
+
const fullKey = resolveKey(prefix, key);
|
|
152
|
+
try {
|
|
153
|
+
await client.send(new commands.PutObjectCommand({
|
|
154
|
+
Bucket: bucket,
|
|
155
|
+
Key: fullKey,
|
|
156
|
+
Body: body,
|
|
157
|
+
ContentType: putOptions.contentType,
|
|
158
|
+
ContentLength: body.byteLength
|
|
159
|
+
}));
|
|
160
|
+
} catch (error) {
|
|
161
|
+
throw wrap("put", key, error);
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
key,
|
|
165
|
+
url: options.publicBaseUrl ? joinPublicUrl(options.publicBaseUrl, fullKey) : void 0,
|
|
166
|
+
size: body.byteLength,
|
|
167
|
+
contentType: putOptions.contentType
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
async get(key) {
|
|
171
|
+
const fullKey = resolveKey(prefix, key);
|
|
172
|
+
if (!commands.GetObjectCommand) throw missingCommand("GetObjectCommand", key, "get");
|
|
173
|
+
try {
|
|
174
|
+
return await readBody(await client.send(new commands.GetObjectCommand({
|
|
175
|
+
Bucket: bucket,
|
|
176
|
+
Key: fullKey
|
|
177
|
+
})));
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (isNotFound$1(error)) return void 0;
|
|
180
|
+
throw wrap("get", key, error);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
async delete(key) {
|
|
184
|
+
const fullKey = resolveKey(prefix, key);
|
|
185
|
+
if (!commands.DeleteObjectCommand) throw missingCommand("DeleteObjectCommand", key, "delete");
|
|
186
|
+
try {
|
|
187
|
+
await client.send(new commands.DeleteObjectCommand({
|
|
188
|
+
Bucket: bucket,
|
|
189
|
+
Key: fullKey
|
|
190
|
+
}));
|
|
191
|
+
} catch (error) {
|
|
192
|
+
throw wrap("delete", key, error);
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
async getUrl(key, urlOptions = {}) {
|
|
196
|
+
const fullKey = resolveKey(prefix, key);
|
|
197
|
+
if (urlOptions.expiresInSeconds === void 0 && options.publicBaseUrl) return joinPublicUrl(options.publicBaseUrl, fullKey);
|
|
198
|
+
if (!options.getSignedUrl || !commands.GetObjectCommand) throw new StorageError("URL készítéséhez add meg a publicBaseUrl opciót, vagy aláírt URL-hez a getSignedUrl függvényt (@aws-sdk/s3-request-presigner) és a commands.GetObjectCommand osztályt.", {
|
|
199
|
+
operation: "getUrl",
|
|
200
|
+
key
|
|
201
|
+
});
|
|
202
|
+
const expiresIn = assertExpires(urlOptions.expiresInSeconds ?? 3600, S3_MAX_PRESIGN_SECONDS, key);
|
|
203
|
+
try {
|
|
204
|
+
return await options.getSignedUrl(client, new commands.GetObjectCommand({
|
|
205
|
+
Bucket: bucket,
|
|
206
|
+
Key: fullKey
|
|
207
|
+
}), { expiresIn });
|
|
208
|
+
} catch (error) {
|
|
209
|
+
throw wrap("getUrl", key, error);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
//#endregion
|
|
215
|
+
//#region src/storage/sigv4.ts
|
|
216
|
+
const AWS_SIGV4_ALGORITHM = "AWS4-HMAC-SHA256";
|
|
217
|
+
const UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";
|
|
218
|
+
const encoder = new TextEncoder();
|
|
219
|
+
function toHex(buffer) {
|
|
220
|
+
return Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
221
|
+
}
|
|
222
|
+
function toBuffer(data) {
|
|
223
|
+
return typeof data === "string" ? toArrayBuffer(encoder.encode(data)) : toArrayBuffer(data);
|
|
224
|
+
}
|
|
225
|
+
async function sha256Hex(data) {
|
|
226
|
+
return toHex(await crypto.subtle.digest("SHA-256", toBuffer(data)));
|
|
227
|
+
}
|
|
228
|
+
async function hmac(key, data) {
|
|
229
|
+
const cryptoKey = await crypto.subtle.importKey("raw", key, {
|
|
230
|
+
name: "HMAC",
|
|
231
|
+
hash: "SHA-256"
|
|
232
|
+
}, false, ["sign"]);
|
|
233
|
+
return crypto.subtle.sign("HMAC", cryptoKey, toBuffer(data));
|
|
234
|
+
}
|
|
235
|
+
function toAmzDate(date) {
|
|
236
|
+
return date.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
237
|
+
}
|
|
238
|
+
function canonicalPath(url) {
|
|
239
|
+
return url.pathname.split("/").map((segment) => encodeRfc3986(decodeURIComponent(segment))).join("/");
|
|
240
|
+
}
|
|
241
|
+
function canonicalQuery(params) {
|
|
242
|
+
return [...params].map(([name, value]) => [encodeRfc3986(name), encodeRfc3986(value)]).sort(([nameA, valueA], [nameB, valueB]) => nameA === nameB ? valueA < valueB ? -1 : 1 : nameA < nameB ? -1 : 1).map(([name, value]) => `${name}=${value}`).join("&");
|
|
243
|
+
}
|
|
244
|
+
function normalizeHeaders(headers) {
|
|
245
|
+
const normalized = /* @__PURE__ */ new Map();
|
|
246
|
+
for (const [name, value] of Object.entries(headers)) normalized.set(name.toLowerCase(), value.trim().replace(/\s+/g, " "));
|
|
247
|
+
return new Map([...normalized].sort(([a], [b]) => a < b ? -1 : 1));
|
|
248
|
+
}
|
|
249
|
+
function canonicalRequest(input) {
|
|
250
|
+
const signedHeaders = [...input.headers.keys()].join(";");
|
|
251
|
+
const headerLines = [...input.headers].map(([name, value]) => `${name}:${value}\n`).join("");
|
|
252
|
+
return {
|
|
253
|
+
request: [
|
|
254
|
+
input.method.toUpperCase(),
|
|
255
|
+
canonicalPath(input.url),
|
|
256
|
+
canonicalQuery(input.url.searchParams),
|
|
257
|
+
headerLines,
|
|
258
|
+
signedHeaders,
|
|
259
|
+
input.payloadHash
|
|
260
|
+
].join("\n"),
|
|
261
|
+
signedHeaders
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
async function signCanonical(scope, amzDate, request) {
|
|
265
|
+
const day = amzDate.slice(0, 8);
|
|
266
|
+
const credentialScope = `${day}/${scope.region}/${scope.service}/aws4_request`;
|
|
267
|
+
const stringToSign = [
|
|
268
|
+
AWS_SIGV4_ALGORITHM,
|
|
269
|
+
amzDate,
|
|
270
|
+
credentialScope,
|
|
271
|
+
await sha256Hex(request)
|
|
272
|
+
].join("\n");
|
|
273
|
+
return {
|
|
274
|
+
signature: toHex(await hmac(await hmac(await hmac(await hmac(await hmac(toBuffer(`AWS4${scope.credentials.secretAccessKey}`), day), scope.region), scope.service), "aws4_request"), stringToSign)),
|
|
275
|
+
credentialScope
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
async function signAwsRequest(input) {
|
|
279
|
+
const url = new URL(input.url);
|
|
280
|
+
const amzDate = toAmzDate(input.date ?? /* @__PURE__ */ new Date());
|
|
281
|
+
const payloadHash = input.payloadHash ?? await sha256Hex(input.body ?? "");
|
|
282
|
+
const extra = {
|
|
283
|
+
host: url.host,
|
|
284
|
+
"x-amz-date": amzDate
|
|
285
|
+
};
|
|
286
|
+
if (input.service === "s3") extra["x-amz-content-sha256"] = payloadHash;
|
|
287
|
+
if (input.credentials.sessionToken) extra["x-amz-security-token"] = input.credentials.sessionToken;
|
|
288
|
+
const headers = normalizeHeaders({
|
|
289
|
+
...input.headers,
|
|
290
|
+
...extra
|
|
291
|
+
});
|
|
292
|
+
const { request, signedHeaders } = canonicalRequest({
|
|
293
|
+
method: input.method,
|
|
294
|
+
url,
|
|
295
|
+
headers,
|
|
296
|
+
payloadHash
|
|
297
|
+
});
|
|
298
|
+
const { signature, credentialScope } = await signCanonical(input, amzDate, request);
|
|
299
|
+
const result = {};
|
|
300
|
+
for (const [name, value] of headers) if (name !== "host") result[name] = value;
|
|
301
|
+
result.authorization = `${AWS_SIGV4_ALGORITHM} Credential=${input.credentials.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
302
|
+
return result;
|
|
303
|
+
}
|
|
304
|
+
async function presignAwsUrl(input) {
|
|
305
|
+
const url = new URL(input.url);
|
|
306
|
+
const amzDate = toAmzDate(input.date ?? /* @__PURE__ */ new Date());
|
|
307
|
+
const credentialScope = `${amzDate.slice(0, 8)}/${input.region}/${input.service}/aws4_request`;
|
|
308
|
+
url.searchParams.set("X-Amz-Algorithm", AWS_SIGV4_ALGORITHM);
|
|
309
|
+
url.searchParams.set("X-Amz-Credential", `${input.credentials.accessKeyId}/${credentialScope}`);
|
|
310
|
+
url.searchParams.set("X-Amz-Date", amzDate);
|
|
311
|
+
url.searchParams.set("X-Amz-Expires", String(input.expiresInSeconds));
|
|
312
|
+
if (input.credentials.sessionToken) url.searchParams.set("X-Amz-Security-Token", input.credentials.sessionToken);
|
|
313
|
+
url.searchParams.set("X-Amz-SignedHeaders", "host");
|
|
314
|
+
const { request } = canonicalRequest({
|
|
315
|
+
method: input.method,
|
|
316
|
+
url,
|
|
317
|
+
headers: /* @__PURE__ */ new Map([["host", url.host]]),
|
|
318
|
+
payloadHash: UNSIGNED_PAYLOAD
|
|
319
|
+
});
|
|
320
|
+
const { signature } = await signCanonical(input, amzDate, request);
|
|
321
|
+
const query = `${canonicalQuery(url.searchParams)}&X-Amz-Signature=${signature}`;
|
|
322
|
+
return `${url.origin}${canonicalPath(url)}?${query}`;
|
|
323
|
+
}
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region src/storage/s3-fetch.ts
|
|
326
|
+
function requireOption(value, name) {
|
|
327
|
+
if (typeof value !== "string" || value.trim() === "") throw new SzamlazzError(`Az s3FetchStorage ${name} opciója kötelező.`, { category: "configuration" });
|
|
328
|
+
return value;
|
|
329
|
+
}
|
|
330
|
+
function bucketBaseUrl(options) {
|
|
331
|
+
const bucket = options.bucket;
|
|
332
|
+
if (options.endpoint === void 0) return options.forcePathStyle ?? bucket.includes(".") ? `https://s3.${options.region}.amazonaws.com/${bucket}` : `https://${bucket}.s3.${options.region}.amazonaws.com`;
|
|
333
|
+
let endpoint;
|
|
334
|
+
try {
|
|
335
|
+
endpoint = new URL(options.endpoint);
|
|
336
|
+
} catch (error) {
|
|
337
|
+
throw new SzamlazzError(`Érvénytelen S3 endpoint: ${options.endpoint}`, {
|
|
338
|
+
category: "configuration",
|
|
339
|
+
cause: error
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
const basePath = endpoint.pathname.replace(/\/+$/, "");
|
|
343
|
+
if (options.forcePathStyle ?? true) return `${endpoint.origin}${basePath}/${bucket}`;
|
|
344
|
+
return `${endpoint.protocol}//${bucket}.${endpoint.host}${basePath}`;
|
|
345
|
+
}
|
|
346
|
+
function readS3ErrorCode(body) {
|
|
347
|
+
const code = /<Code>([^<]*)<\/Code>/.exec(body)?.[1];
|
|
348
|
+
const message = /<Message>([^<]*)<\/Message>/.exec(body)?.[1];
|
|
349
|
+
if (!code) return void 0;
|
|
350
|
+
return message ? `${code}: ${message}` : code;
|
|
351
|
+
}
|
|
352
|
+
function s3FetchStorage(options) {
|
|
353
|
+
requireOption(options.bucket, "bucket");
|
|
354
|
+
requireOption(options.region, "region");
|
|
355
|
+
const credentials = {
|
|
356
|
+
accessKeyId: requireOption(options.accessKeyId, "accessKeyId"),
|
|
357
|
+
secretAccessKey: requireOption(options.secretAccessKey, "secretAccessKey"),
|
|
358
|
+
sessionToken: options.sessionToken
|
|
359
|
+
};
|
|
360
|
+
const baseUrl = bucketBaseUrl(options);
|
|
361
|
+
const prefix = normalizePrefix(options.prefix);
|
|
362
|
+
const scope = {
|
|
363
|
+
credentials,
|
|
364
|
+
region: options.region,
|
|
365
|
+
service: "s3"
|
|
366
|
+
};
|
|
367
|
+
const objectUrl = (fullKey) => `${baseUrl}/${encodeKeyPath(fullKey)}`;
|
|
368
|
+
async function send(operation, key, init) {
|
|
369
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
370
|
+
try {
|
|
371
|
+
return await doFetch(init.url, {
|
|
372
|
+
method: init.method,
|
|
373
|
+
headers: init.headers,
|
|
374
|
+
...init.body === void 0 ? {} : { body: init.body }
|
|
375
|
+
});
|
|
376
|
+
} catch (error) {
|
|
377
|
+
throw new StorageError(`S3 hálózati hiba (${operation}): ${describeError(error)}`, {
|
|
378
|
+
operation,
|
|
379
|
+
key,
|
|
380
|
+
cause: error
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
async function failure(operation, key, response) {
|
|
385
|
+
const detail = readS3ErrorCode(await response.text().catch(() => "")) ?? response.statusText;
|
|
386
|
+
return new StorageError(`S3 hiba (${operation}, HTTP ${response.status})${detail ? `: ${detail}` : ""}`, {
|
|
387
|
+
operation,
|
|
388
|
+
key,
|
|
389
|
+
status: response.status
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
async put(key, body, putOptions) {
|
|
394
|
+
const fullKey = resolveKey(prefix, key);
|
|
395
|
+
const url = objectUrl(fullKey);
|
|
396
|
+
const response = await send("put", key, {
|
|
397
|
+
method: "PUT",
|
|
398
|
+
url,
|
|
399
|
+
headers: await signAwsRequest({
|
|
400
|
+
...scope,
|
|
401
|
+
method: "PUT",
|
|
402
|
+
url,
|
|
403
|
+
headers: { "content-type": putOptions.contentType },
|
|
404
|
+
payloadHash: await sha256Hex(body)
|
|
405
|
+
}),
|
|
406
|
+
body: toArrayBuffer(body)
|
|
407
|
+
});
|
|
408
|
+
if (!response.ok) throw await failure("put", key, response);
|
|
409
|
+
return {
|
|
410
|
+
key,
|
|
411
|
+
url: options.publicBaseUrl ? joinPublicUrl(options.publicBaseUrl, fullKey) : void 0,
|
|
412
|
+
size: body.byteLength,
|
|
413
|
+
contentType: putOptions.contentType
|
|
414
|
+
};
|
|
415
|
+
},
|
|
416
|
+
async get(key) {
|
|
417
|
+
const url = objectUrl(resolveKey(prefix, key));
|
|
418
|
+
const response = await send("get", key, {
|
|
419
|
+
method: "GET",
|
|
420
|
+
url,
|
|
421
|
+
headers: await signAwsRequest({
|
|
422
|
+
...scope,
|
|
423
|
+
method: "GET",
|
|
424
|
+
url
|
|
425
|
+
})
|
|
426
|
+
});
|
|
427
|
+
if (response.status === 404) return void 0;
|
|
428
|
+
if (!response.ok) throw await failure("get", key, response);
|
|
429
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
430
|
+
},
|
|
431
|
+
async delete(key) {
|
|
432
|
+
const url = objectUrl(resolveKey(prefix, key));
|
|
433
|
+
const response = await send("delete", key, {
|
|
434
|
+
method: "DELETE",
|
|
435
|
+
url,
|
|
436
|
+
headers: await signAwsRequest({
|
|
437
|
+
...scope,
|
|
438
|
+
method: "DELETE",
|
|
439
|
+
url
|
|
440
|
+
})
|
|
441
|
+
});
|
|
442
|
+
if (!response.ok && response.status !== 404) throw await failure("delete", key, response);
|
|
443
|
+
},
|
|
444
|
+
async getUrl(key, urlOptions = {}) {
|
|
445
|
+
const fullKey = resolveKey(prefix, key);
|
|
446
|
+
if (urlOptions.expiresInSeconds === void 0 && options.publicBaseUrl) return joinPublicUrl(options.publicBaseUrl, fullKey);
|
|
447
|
+
return presignAwsUrl({
|
|
448
|
+
...scope,
|
|
449
|
+
method: "GET",
|
|
450
|
+
url: objectUrl(fullKey),
|
|
451
|
+
expiresInSeconds: assertExpires(urlOptions.expiresInSeconds ?? 3600, S3_MAX_PRESIGN_SECONDS, key)
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
//#endregion
|
|
457
|
+
//#region src/storage/store-pdf.ts
|
|
458
|
+
async function storePdf(storage, key, pdf) {
|
|
459
|
+
if (!isPdf(pdf)) throw new StorageError("A tárolandó tartalom nem PDF (hiányzik a %PDF fejléc).", {
|
|
460
|
+
operation: "put",
|
|
461
|
+
key
|
|
462
|
+
});
|
|
463
|
+
return storage.put(key, pdf, { contentType: PDF_CONTENT_TYPE });
|
|
464
|
+
}
|
|
465
|
+
//#endregion
|
|
466
|
+
//#region src/storage/supabase.ts
|
|
467
|
+
const SUPABASE_MAX_SIGNED_URL_SECONDS = 31536e3;
|
|
468
|
+
function isNotFound(error) {
|
|
469
|
+
return error.status === 404 || error.statusCode === "404" || /not[\s_-]?found/i.test(error.message);
|
|
470
|
+
}
|
|
471
|
+
const run = (operation, key, call) => guardStorageCall("Supabase Storage", operation, key, call);
|
|
472
|
+
function failure(operation, key, error) {
|
|
473
|
+
return new StorageError(`Supabase Storage hiba (${operation}): ${error?.message ?? "üres válasz"}`, {
|
|
474
|
+
operation,
|
|
475
|
+
key,
|
|
476
|
+
status: error?.status,
|
|
477
|
+
cause: error ?? void 0
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
function supabaseStorage(options) {
|
|
481
|
+
const prefix = normalizePrefix(options.prefix);
|
|
482
|
+
const bucket = () => options.client.storage.from(options.bucket);
|
|
483
|
+
return {
|
|
484
|
+
async put(key, body, putOptions) {
|
|
485
|
+
const path = resolveKey(prefix, key);
|
|
486
|
+
const uploadOptions = {
|
|
487
|
+
contentType: putOptions.contentType,
|
|
488
|
+
upsert: options.upsert ?? true,
|
|
489
|
+
...options.cacheControl === void 0 ? {} : { cacheControl: options.cacheControl }
|
|
490
|
+
};
|
|
491
|
+
const result = await run("put", key, () => bucket().upload(path, body, uploadOptions));
|
|
492
|
+
if (!result.data) throw failure("put", key, result.error);
|
|
493
|
+
return {
|
|
494
|
+
key,
|
|
495
|
+
url: options.public ? bucket().getPublicUrl(path).data.publicUrl : void 0,
|
|
496
|
+
size: body.byteLength,
|
|
497
|
+
contentType: putOptions.contentType
|
|
498
|
+
};
|
|
499
|
+
},
|
|
500
|
+
async get(key) {
|
|
501
|
+
const path = resolveKey(prefix, key);
|
|
502
|
+
const result = await run("get", key, () => bucket().download(path));
|
|
503
|
+
if (!result.data) {
|
|
504
|
+
if (result.error && isNotFound(result.error)) return void 0;
|
|
505
|
+
throw failure("get", key, result.error);
|
|
506
|
+
}
|
|
507
|
+
const blob = result.data;
|
|
508
|
+
return new Uint8Array(await blob.arrayBuffer());
|
|
509
|
+
},
|
|
510
|
+
async delete(key) {
|
|
511
|
+
const path = resolveKey(prefix, key);
|
|
512
|
+
const result = await run("delete", key, () => bucket().remove([path]));
|
|
513
|
+
if (result.error) throw failure("delete", key, result.error);
|
|
514
|
+
},
|
|
515
|
+
async getUrl(key, urlOptions = {}) {
|
|
516
|
+
const path = resolveKey(prefix, key);
|
|
517
|
+
if (options.public && urlOptions.expiresInSeconds === void 0) return bucket().getPublicUrl(path).data.publicUrl;
|
|
518
|
+
const expiresIn = assertExpires(urlOptions.expiresInSeconds ?? 3600, SUPABASE_MAX_SIGNED_URL_SECONDS, key);
|
|
519
|
+
const result = await run("getUrl", key, () => bucket().createSignedUrl(path, expiresIn));
|
|
520
|
+
if (!result.data) throw failure("getUrl", key, result.error);
|
|
521
|
+
return result.data.signedUrl;
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
//#endregion
|
|
526
|
+
//#region src/storage/uploadthing.ts
|
|
527
|
+
const UPLOADTHING_MAX_CUSTOM_ID_LENGTH = 128;
|
|
528
|
+
const UPLOADTHING_MAX_SIGNED_URL_SECONDS = 604800;
|
|
529
|
+
function uploadthingStorage(options) {
|
|
530
|
+
const { utapi } = options;
|
|
531
|
+
const prefix = normalizePrefix(options.prefix);
|
|
532
|
+
const uploadOptions = {
|
|
533
|
+
...options.acl === void 0 ? {} : { acl: options.acl },
|
|
534
|
+
...options.contentDisposition === void 0 ? {} : { contentDisposition: options.contentDisposition }
|
|
535
|
+
};
|
|
536
|
+
return {
|
|
537
|
+
async put(key, body, putOptions) {
|
|
538
|
+
const customId = resolveKey(prefix, key);
|
|
539
|
+
if (customId.length > 128) throw new StorageError(`Az UploadThing customId legfeljebb 128 karakter lehet, a kulcs ${customId.length} karakteres.`, {
|
|
540
|
+
operation: "key",
|
|
541
|
+
key
|
|
542
|
+
});
|
|
543
|
+
const file = Object.assign(new File([toArrayBuffer(body)], basename(customId), { type: putOptions.contentType }), { customId });
|
|
544
|
+
const result = await guardStorageCall("UploadThing", "put", key, () => utapi.uploadFiles(file, uploadOptions));
|
|
545
|
+
if (!result.data) throw new StorageError(`UploadThing hiba (put): ${result.error?.message ?? "ismeretlen hiba"}`, {
|
|
546
|
+
operation: "put",
|
|
547
|
+
key,
|
|
548
|
+
cause: result.error
|
|
549
|
+
});
|
|
550
|
+
return {
|
|
551
|
+
key,
|
|
552
|
+
url: result.data.ufsUrl ?? result.data.url,
|
|
553
|
+
size: result.data.size,
|
|
554
|
+
contentType: putOptions.contentType
|
|
555
|
+
};
|
|
556
|
+
},
|
|
557
|
+
async delete(key) {
|
|
558
|
+
const customId = resolveKey(prefix, key);
|
|
559
|
+
await guardStorageCall("UploadThing", "delete", key, () => utapi.deleteFiles(customId, { keyType: "customId" }));
|
|
560
|
+
},
|
|
561
|
+
async getUrl(key, urlOptions = {}) {
|
|
562
|
+
const customId = resolveKey(prefix, key);
|
|
563
|
+
const generate = utapi.generateSignedURL;
|
|
564
|
+
if (!generate) throw new StorageError("Ez az UploadThing verzió nem támogatja a generateSignedURL-t, frissíts uploadthing v7-re.", {
|
|
565
|
+
operation: "getUrl",
|
|
566
|
+
key
|
|
567
|
+
});
|
|
568
|
+
const expiresIn = assertExpires(urlOptions.expiresInSeconds ?? 3600, UPLOADTHING_MAX_SIGNED_URL_SECONDS, key);
|
|
569
|
+
return (await guardStorageCall("UploadThing", "getUrl", key, () => generate.call(utapi, customId, { expiresIn }))).ufsUrl;
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
//#endregion
|
|
574
|
+
//#region src/storage/vercel-blob.ts
|
|
575
|
+
function missingFunction(name, key, operation) {
|
|
576
|
+
return new StorageError(`Ehhez a művelethez add át a @vercel/blob ${name} függvényét a vercelBlobStorage-nak.`, {
|
|
577
|
+
operation,
|
|
578
|
+
key
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
function vercelBlobStorage(options) {
|
|
582
|
+
const prefix = normalizePrefix(options.prefix);
|
|
583
|
+
const commandOptions = options.token ? { token: options.token } : {};
|
|
584
|
+
return {
|
|
585
|
+
async put(key, body, putOptions) {
|
|
586
|
+
const fullKey = resolveKey(prefix, key);
|
|
587
|
+
const blobOptions = {
|
|
588
|
+
access: options.access,
|
|
589
|
+
contentType: putOptions.contentType,
|
|
590
|
+
addRandomSuffix: false,
|
|
591
|
+
allowOverwrite: options.allowOverwrite ?? true,
|
|
592
|
+
...commandOptions,
|
|
593
|
+
...options.cacheControlMaxAge === void 0 ? {} : { cacheControlMaxAge: options.cacheControlMaxAge }
|
|
594
|
+
};
|
|
595
|
+
const blob = new Blob([toArrayBuffer(body)], { type: putOptions.contentType });
|
|
596
|
+
return {
|
|
597
|
+
key,
|
|
598
|
+
url: (await guardStorageCall("Vercel Blob", "put", key, () => options.put(fullKey, blob, blobOptions))).url,
|
|
599
|
+
size: body.byteLength,
|
|
600
|
+
contentType: putOptions.contentType
|
|
601
|
+
};
|
|
602
|
+
},
|
|
603
|
+
async delete(key) {
|
|
604
|
+
const fullKey = resolveKey(prefix, key);
|
|
605
|
+
const del = options.del;
|
|
606
|
+
if (!del) throw missingFunction("del", key, "delete");
|
|
607
|
+
await guardStorageCall("Vercel Blob", "delete", key, () => del(fullKey, commandOptions));
|
|
608
|
+
},
|
|
609
|
+
async getUrl(key, urlOptions = {}) {
|
|
610
|
+
const fullKey = resolveKey(prefix, key);
|
|
611
|
+
if (urlOptions.expiresInSeconds !== void 0) throw unsupportedSignedUrl("vercelBlobStorage", key);
|
|
612
|
+
const head = options.head;
|
|
613
|
+
if (!head) throw missingFunction("head", key, "getUrl");
|
|
614
|
+
return (await guardStorageCall("Vercel Blob", "getUrl", key, () => head(fullKey, commandOptions))).url;
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
//#endregion
|
|
619
|
+
export { DEFAULT_INVOICE_KEY_PREFIX, DEFAULT_RECEIPT_KEY_PREFIX, PDF_CONTENT_TYPE, S3_MAX_PRESIGN_SECONDS, SUPABASE_MAX_SIGNED_URL_SECONDS, StorageError, UPLOADTHING_MAX_CUSTOM_ID_LENGTH, UPLOADTHING_MAX_SIGNED_URL_SECONDS, assertStorageKey, guardStorageCall, invoicePdfKey, isStorageError, memoryStorage, presignAwsUrl, r2BindingStorage, receiptPdfKey, s3FetchStorage, s3Storage, sanitizeKeySegment, signAwsRequest, storePdf, supabaseStorage, uploadthingStorage, vercelBlobStorage };
|