@webtwizz/cloud 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/db.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import postgres from 'postgres';
2
+
3
+ type DbOptions = postgres.Options<Record<string, postgres.PostgresType>> & {
4
+ url?: string;
5
+ };
6
+ type Db = postgres.Sql<Record<string, never>>;
7
+ declare function createDb({ url, ...options }?: DbOptions): Db;
8
+
9
+ export { type Db, type DbOptions, createDb };
package/dist/db.js ADDED
@@ -0,0 +1,13 @@
1
+ import {
2
+ env
3
+ } from "./chunk-ESAV545E.js";
4
+
5
+ // src/db.ts
6
+ import "server-only";
7
+ import postgres from "postgres";
8
+ function createDb({ url, ...options } = {}) {
9
+ return postgres(url ?? env("DATABASE_URL"), { ...options, prepare: false });
10
+ }
11
+ export {
12
+ createDb
13
+ };
@@ -0,0 +1,35 @@
1
+ type Bucket = 'public' | 'private';
2
+ type UploadRequest = {
3
+ bucket: Bucket;
4
+ name: string;
5
+ type: string;
6
+ size: number;
7
+ folder?: string;
8
+ accept?: string[];
9
+ };
10
+ type Upload = {
11
+ url: string;
12
+ key: string;
13
+ headers: Record<string, string>;
14
+ };
15
+ declare class FileError extends Error {
16
+ code: 'too_large' | 'type';
17
+ constructor(code: 'too_large' | 'type', message: string);
18
+ }
19
+ declare function safeName(name: string): string;
20
+ declare function accepts(type: string, accept: string[]): boolean;
21
+ declare function createFiles(): {
22
+ maxFileMb: number;
23
+ /** A 5-minute link on the files address the browser PUTs the file to, with the exact headers it must send. */
24
+ uploadUrl({ bucket, name, type, size, folder, accept }: UploadRequest): Promise<Upload>;
25
+ publicUrl(key: string): string;
26
+ /** A short-lived link to a private file; check the visitor may see it first. */
27
+ privateUrl(key: string, { expiresIn, download }?: {
28
+ expiresIn?: number;
29
+ download?: string;
30
+ }): Promise<string>;
31
+ remove(bucket: Bucket, key: string): Promise<void>;
32
+ };
33
+ type Files = ReturnType<typeof createFiles>;
34
+
35
+ export { type Bucket, FileError, type Files, type Upload, type UploadRequest, accepts, createFiles, safeName };
package/dist/files.js ADDED
@@ -0,0 +1,92 @@
1
+ import {
2
+ MESSAGES
3
+ } from "./chunk-G54JBUO3.js";
4
+ import {
5
+ env
6
+ } from "./chunk-ESAV545E.js";
7
+
8
+ // src/files.ts
9
+ import "server-only";
10
+ import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
11
+ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
12
+ var FileError = class extends Error {
13
+ constructor(code, message) {
14
+ super(message);
15
+ this.code = code;
16
+ this.name = "FileError";
17
+ }
18
+ code;
19
+ };
20
+ var IMMUTABLE = "public, max-age=31536000, immutable";
21
+ function safeName(name) {
22
+ const cleaned = name.normalize("NFKD").replace(/[^\w.-]+/g, "-").replace(/-+/g, "-").replace(/^[-.]+|-+$/g, "");
23
+ return cleaned.slice(-80) || "file";
24
+ }
25
+ function accepts(type, accept) {
26
+ return accept.some((rule) => rule.endsWith("/*") ? type.startsWith(rule.slice(0, -1)) : type === rule);
27
+ }
28
+ function encodeKey(key) {
29
+ return key.split("/").map(encodeURIComponent).join("/");
30
+ }
31
+ function createFiles() {
32
+ const endpoint = env("WEBTWIZZ_STORAGE_ENDPOINT").replace(/\/+$/, "");
33
+ const filesUrl = (process.env.WEBTWIZZ_FILES_URL || endpoint).replace(/\/+$/, "");
34
+ const region = env("WEBTWIZZ_STORAGE_REGION");
35
+ const client = (key, secret) => new S3Client({
36
+ region,
37
+ endpoint,
38
+ forcePathStyle: true,
39
+ requestChecksumCalculation: "WHEN_REQUIRED",
40
+ credentials: { accessKeyId: env(key), secretAccessKey: env(secret) }
41
+ });
42
+ const writer = client("WEBTWIZZ_STORAGE_KEY", "WEBTWIZZ_STORAGE_SECRET");
43
+ let reader;
44
+ const maxFileMb = Number(process.env.WEBTWIZZ_STORAGE_MAX_FILE_MB) || 0;
45
+ const onFilesAddress = (url) => url.startsWith(`${endpoint}/`) ? filesUrl + url.slice(endpoint.length) : url;
46
+ return {
47
+ maxFileMb,
48
+ /** A 5-minute link on the files address the browser PUTs the file to, with the exact headers it must send. */
49
+ async uploadUrl({ bucket, name, type, size, folder, accept }) {
50
+ if (maxFileMb && size > maxFileMb * 1024 * 1024) throw new FileError("too_large", MESSAGES.fileTooLarge(maxFileMb));
51
+ if (accept && !accepts(type, accept)) throw new FileError("type", MESSAGES.fileType);
52
+ const prefix = folder ? `${folder.split("/").map(safeName).join("/")}/` : "";
53
+ const key = `${prefix}${crypto.randomUUID()}-${safeName(name)}`;
54
+ const headers = { "Content-Type": type };
55
+ if (bucket === "public") headers["Cache-Control"] = IMMUTABLE;
56
+ const command = new PutObjectCommand({
57
+ Bucket: bucket,
58
+ Key: key,
59
+ ContentType: type,
60
+ ContentLength: size,
61
+ CacheControl: headers["Cache-Control"]
62
+ });
63
+ const signed = await getSignedUrl(writer, command, {
64
+ expiresIn: 300,
65
+ signableHeaders: /* @__PURE__ */ new Set(["content-type", "content-length"])
66
+ });
67
+ return { url: onFilesAddress(signed), key, headers };
68
+ },
69
+ publicUrl(key) {
70
+ return `${filesUrl}/public/${encodeKey(key)}`;
71
+ },
72
+ /** A short-lived link to a private file; check the visitor may see it first. */
73
+ async privateUrl(key, { expiresIn = 300, download } = {}) {
74
+ reader ??= client("WEBTWIZZ_STORAGE_READ_KEY", "WEBTWIZZ_STORAGE_READ_SECRET");
75
+ const command = new GetObjectCommand({
76
+ Bucket: "private",
77
+ Key: key,
78
+ ResponseContentDisposition: download ? `attachment; filename="${safeName(download)}"` : void 0
79
+ });
80
+ return onFilesAddress(await getSignedUrl(reader, command, { expiresIn }));
81
+ },
82
+ async remove(bucket, key) {
83
+ await writer.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
84
+ }
85
+ };
86
+ }
87
+ export {
88
+ FileError,
89
+ accepts,
90
+ createFiles,
91
+ safeName
92
+ };
@@ -0,0 +1,16 @@
1
+ type CloudError = {
2
+ code?: string;
3
+ status?: number;
4
+ } | null | undefined;
5
+ declare const AUTH_MESSAGES: Record<string, string>;
6
+ /** Plain words for a sign-in error, never the library's own text. */
7
+ declare function authMessage(error: CloudError, overrides?: Record<string, string>): string;
8
+ declare const MESSAGES: {
9
+ readonly unavailable: "This isn't available right now. Please try again later.";
10
+ readonly saveFailed: "Couldn't save right now. Please try again later.";
11
+ readonly uploadsPaused: "Uploads are paused right now. Please try again later.";
12
+ readonly fileType: "That type of file can't be uploaded here.";
13
+ readonly fileTooLarge: (mb: number) => string;
14
+ };
15
+
16
+ export { AUTH_MESSAGES, type CloudError, MESSAGES, authMessage };
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ import {
2
+ AUTH_MESSAGES,
3
+ MESSAGES,
4
+ authMessage
5
+ } from "./chunk-G54JBUO3.js";
6
+ export {
7
+ AUTH_MESSAGES,
8
+ MESSAGES,
9
+ authMessage
10
+ };
@@ -0,0 +1,13 @@
1
+ import { createNeonAuth } from '@neondatabase/auth/next/server';
2
+
3
+ type AuthOptions = {
4
+ url?: string;
5
+ secret?: string;
6
+ sessionDataTtl?: number;
7
+ };
8
+ type Auth = ReturnType<typeof createNeonAuth>;
9
+ /** What visitors get back: WebTwizz cookie names and none of the sign-in service's own headers. */
10
+ declare function forBrowser<T extends Response>(response: T): T;
11
+ declare function createAuth(options?: AuthOptions): Auth;
12
+
13
+ export { type Auth, type AuthOptions, createAuth, forBrowser };