dineway 0.1.3

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.
Files changed (96) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +89 -0
  3. package/dist/adapters-BlzWJG82.d.mts +106 -0
  4. package/dist/apply-CAPvMfoU.mjs +1339 -0
  5. package/dist/astro/index.d.mts +50 -0
  6. package/dist/astro/index.mjs +1326 -0
  7. package/dist/astro/middleware/auth.d.mts +30 -0
  8. package/dist/astro/middleware/auth.mjs +708 -0
  9. package/dist/astro/middleware/redirect.d.mts +21 -0
  10. package/dist/astro/middleware/redirect.mjs +62 -0
  11. package/dist/astro/middleware/request-context.d.mts +17 -0
  12. package/dist/astro/middleware/request-context.mjs +1371 -0
  13. package/dist/astro/middleware/setup.d.mts +19 -0
  14. package/dist/astro/middleware/setup.mjs +46 -0
  15. package/dist/astro/middleware.d.mts +12 -0
  16. package/dist/astro/middleware.mjs +1716 -0
  17. package/dist/astro/types.d.mts +269 -0
  18. package/dist/astro/types.mjs +1 -0
  19. package/dist/base64-F8-DUraK.mjs +58 -0
  20. package/dist/byline-DeWCMU_i.mjs +234 -0
  21. package/dist/bylines-DyqBV9EQ.mjs +137 -0
  22. package/dist/chunk-ClPoSABd.mjs +21 -0
  23. package/dist/cli/index.d.mts +1 -0
  24. package/dist/cli/index.mjs +3987 -0
  25. package/dist/client/external-auth-headers.d.mts +38 -0
  26. package/dist/client/external-auth-headers.mjs +101 -0
  27. package/dist/client/index.d.mts +397 -0
  28. package/dist/client/index.mjs +345 -0
  29. package/dist/config-Cq8H0SfX.mjs +46 -0
  30. package/dist/connection-C9pxzuag.mjs +52 -0
  31. package/dist/content-zSgdNmnt.mjs +836 -0
  32. package/dist/db/index.d.mts +4 -0
  33. package/dist/db/index.mjs +62 -0
  34. package/dist/db/libsql.d.mts +10 -0
  35. package/dist/db/libsql.mjs +21 -0
  36. package/dist/db/postgres.d.mts +10 -0
  37. package/dist/db/postgres.mjs +29 -0
  38. package/dist/db/sqlite.d.mts +10 -0
  39. package/dist/db/sqlite.mjs +15 -0
  40. package/dist/default-WYlzADZL.mjs +80 -0
  41. package/dist/dialect-helpers-B9uSp2GJ.mjs +89 -0
  42. package/dist/error-DrxtnGPg.mjs +26 -0
  43. package/dist/index-C-jx21qs.d.mts +4771 -0
  44. package/dist/index.d.mts +16 -0
  45. package/dist/index.mjs +30 -0
  46. package/dist/load-C6FCD1FU.mjs +27 -0
  47. package/dist/loader-qKmo0wAY.mjs +446 -0
  48. package/dist/manifest-schema-CTSEyIJ3.mjs +186 -0
  49. package/dist/media/index.d.mts +25 -0
  50. package/dist/media/index.mjs +54 -0
  51. package/dist/media/local-runtime.d.mts +38 -0
  52. package/dist/media/local-runtime.mjs +132 -0
  53. package/dist/media-DMTr80Gv.mjs +199 -0
  54. package/dist/mode-BlyYtIFO.mjs +22 -0
  55. package/dist/page/index.d.mts +148 -0
  56. package/dist/page/index.mjs +419 -0
  57. package/dist/placeholder-B3knXwNc.mjs +267 -0
  58. package/dist/placeholder-bOx1xCTY.d.mts +283 -0
  59. package/dist/plugin-utils.d.mts +57 -0
  60. package/dist/plugin-utils.mjs +77 -0
  61. package/dist/plugins/adapt-sandbox-entry.d.mts +21 -0
  62. package/dist/plugins/adapt-sandbox-entry.mjs +112 -0
  63. package/dist/query-BiaPl_g2.mjs +459 -0
  64. package/dist/redirect-JPqLAbxa.mjs +328 -0
  65. package/dist/registry-DSd1GWB8.mjs +851 -0
  66. package/dist/request-context.d.mts +49 -0
  67. package/dist/request-context.mjs +42 -0
  68. package/dist/runner-B5l1JfOj.d.mts +26 -0
  69. package/dist/runner-BGUGywgG.mjs +1529 -0
  70. package/dist/runtime.d.mts +25 -0
  71. package/dist/runtime.mjs +41 -0
  72. package/dist/search-BNruJHDL.mjs +11054 -0
  73. package/dist/seed/index.d.mts +3 -0
  74. package/dist/seed/index.mjs +15 -0
  75. package/dist/seo/index.d.mts +69 -0
  76. package/dist/seo/index.mjs +69 -0
  77. package/dist/storage/local.d.mts +38 -0
  78. package/dist/storage/local.mjs +165 -0
  79. package/dist/storage/s3.d.mts +31 -0
  80. package/dist/storage/s3.mjs +174 -0
  81. package/dist/tokens-4vgYuXsZ.mjs +170 -0
  82. package/dist/transport-C5FYnid7.mjs +417 -0
  83. package/dist/transport-gIL-e43D.d.mts +41 -0
  84. package/dist/types-BawVha09.mjs +30 -0
  85. package/dist/types-BgQeVaPj.d.mts +192 -0
  86. package/dist/types-CLLdsG3g.d.mts +103 -0
  87. package/dist/types-D38djUXv.d.mts +1196 -0
  88. package/dist/types-DShnjzb6.mjs +15 -0
  89. package/dist/types-DkvMXalq.d.mts +425 -0
  90. package/dist/types-DuNbGKjF.mjs +74 -0
  91. package/dist/types-ju-_ORz7.d.mts +182 -0
  92. package/dist/validate-CXnRKfJK.mjs +327 -0
  93. package/dist/validate-CqRJb_xU.mjs +96 -0
  94. package/dist/validate-DVKJJ-M_.d.mts +377 -0
  95. package/locals.d.ts +47 -0
  96. package/package.json +313 -0
@@ -0,0 +1,3 @@
1
+ import "../types-DkvMXalq.mjs";
2
+ import { _ as SeedTaxonomyTerm, a as applySeed, b as ValidationResult, c as SeedCollection, d as SeedFile, f as SeedMenu, g as SeedTaxonomy, h as SeedSection, i as defaultSeed, l as SeedContentEntry, m as SeedRedirect, n as loadSeed, o as SeedApplyOptions, p as SeedMenuItem, r as loadUserSeed, s as SeedApplyResult, t as validateSeed, u as SeedField, v as SeedWidget, y as SeedWidgetArea } from "../validate-DVKJJ-M_.mjs";
3
+ export { type SeedApplyOptions, type SeedApplyResult, type SeedCollection, type SeedContentEntry, type SeedField, type SeedFile, type SeedMenu, type SeedMenuItem, type SeedRedirect, type SeedSection, type SeedTaxonomy, type SeedTaxonomyTerm, type SeedWidget, type SeedWidgetArea, type ValidationResult, applySeed, defaultSeed, loadSeed, loadUserSeed, validateSeed };
@@ -0,0 +1,15 @@
1
+ import "../dialect-helpers-B9uSp2GJ.mjs";
2
+ import "../content-zSgdNmnt.mjs";
3
+ import "../base64-F8-DUraK.mjs";
4
+ import "../types-BawVha09.mjs";
5
+ import "../media-DMTr80Gv.mjs";
6
+ import { t as applySeed } from "../apply-CAPvMfoU.mjs";
7
+ import "../registry-DSd1GWB8.mjs";
8
+ import "../redirect-JPqLAbxa.mjs";
9
+ import "../byline-DeWCMU_i.mjs";
10
+ import "../loader-qKmo0wAY.mjs";
11
+ import { t as validateSeed } from "../validate-CXnRKfJK.mjs";
12
+ import { t as defaultSeed } from "../default-WYlzADZL.mjs";
13
+ import { n as loadUserSeed, t as loadSeed } from "../load-C6FCD1FU.mjs";
14
+
15
+ export { applySeed, defaultSeed, loadSeed, loadUserSeed, validateSeed };
@@ -0,0 +1,69 @@
1
+ import { i as ContentSeo } from "../types-CLLdsG3g.mjs";
2
+
3
+ //#region src/seo/index.d.ts
4
+ /**
5
+ * Content input for SEO functions.
6
+ * Accepts both ContentEntry<T> (from query functions) and ContentItem (internal).
7
+ */
8
+ interface SeoContentInput<T = Record<string, unknown>> {
9
+ /** Content data object */
10
+ data: T & {
11
+ title?: unknown;
12
+ excerpt?: unknown;
13
+ seo?: ContentSeo;
14
+ };
15
+ /** SEO metadata (legacy location, prefer data.seo) */
16
+ seo?: ContentSeo;
17
+ }
18
+ /** Resolved SEO meta tags ready for use in templates */
19
+ interface SeoMeta {
20
+ /** Full <title> tag content (e.g., "Post Title | Site Name") */
21
+ title: string;
22
+ /** Meta description */
23
+ description: string | null;
24
+ /** OG title (same as title by default) */
25
+ ogTitle: string;
26
+ /** OG description */
27
+ ogDescription: string | null;
28
+ /** OG image URL (absolute) */
29
+ ogImage: string | null;
30
+ /** Canonical URL */
31
+ canonical: string | null;
32
+ /** Robots directive (e.g., "noindex, nofollow") or null if default */
33
+ robots: string | null;
34
+ }
35
+ /** Options for generating SEO meta from a content item */
36
+ interface SeoMetaOptions {
37
+ /** Site title for the suffix (e.g., "My Blog") */
38
+ siteTitle?: string;
39
+ /** Site URL origin for building absolute URLs (e.g., "https://example.com") */
40
+ siteUrl?: string;
41
+ /** Title separator between page title and site title */
42
+ titleSeparator?: string;
43
+ /** Path to this content (e.g., "/posts/my-post") for canonical fallback */
44
+ path?: string;
45
+ /** Default OG image URL if content has none */
46
+ defaultOgImage?: string;
47
+ }
48
+ /**
49
+ * Generate resolved SEO meta tags from a content item.
50
+ *
51
+ * Uses the content item's SEO fields, falling back to content data
52
+ * (title from `data.title`, description from `data.excerpt`).
53
+ *
54
+ * @param content - The content item (from getDinewayEntry, etc.)
55
+ * @param options - Configuration for title construction, canonical URLs, etc.
56
+ * @returns Resolved meta tags ready for template use
57
+ */
58
+ declare function getSeoMeta<T>(content: SeoContentInput<T>, options?: SeoMetaOptions): SeoMeta;
59
+ /**
60
+ * Extract SEO data from a content item.
61
+ *
62
+ * Convenience accessor for the raw SEO fields without template resolution.
63
+ *
64
+ * @param content - The content item
65
+ * @returns The content's SEO fields
66
+ */
67
+ declare function getContentSeo<T>(content: SeoContentInput<T>): ContentSeo | undefined;
68
+ //#endregion
69
+ export { SeoContentInput, SeoMeta, SeoMetaOptions, getContentSeo, getSeoMeta };
@@ -0,0 +1,69 @@
1
+ //#region src/seo/index.ts
2
+ const TRAILING_SLASH_RE = /\/$/;
3
+ const ABSOLUTE_URL_RE = /^https?:\/\//i;
4
+ /**
5
+ * Generate resolved SEO meta tags from a content item.
6
+ *
7
+ * Uses the content item's SEO fields, falling back to content data
8
+ * (title from `data.title`, description from `data.excerpt`).
9
+ *
10
+ * @param content - The content item (from getDinewayEntry, etc.)
11
+ * @param options - Configuration for title construction, canonical URLs, etc.
12
+ * @returns Resolved meta tags ready for template use
13
+ */
14
+ function getSeoMeta(content, options = {}) {
15
+ const { siteTitle, siteUrl, path, defaultOgImage } = options;
16
+ const separator = options.titleSeparator || " | ";
17
+ const seo = content.seo ?? content.data.seo ?? {
18
+ title: null,
19
+ description: null,
20
+ image: null,
21
+ canonical: null,
22
+ noIndex: false
23
+ };
24
+ const pageTitle = seo.title || (typeof content.data.title === "string" ? content.data.title : null) || "";
25
+ const fullTitle = siteTitle && pageTitle ? `${pageTitle}${separator}${siteTitle}` : pageTitle;
26
+ const description = seo.description || (typeof content.data.excerpt === "string" ? content.data.excerpt : null) || null;
27
+ const ogImage = seo.image ? buildMediaUrl(seo.image, siteUrl) : defaultOgImage ?? null;
28
+ let canonical = null;
29
+ if (seo.canonical) if (siteUrl && !seo.canonical.startsWith("/") && !ABSOLUTE_URL_RE.test(seo.canonical)) canonical = `${siteUrl.replace(TRAILING_SLASH_RE, "")}/${seo.canonical}`;
30
+ else canonical = seo.canonical;
31
+ else if (siteUrl && path) {
32
+ const safePath = path.startsWith("/") ? path : `/${path}`;
33
+ canonical = `${siteUrl.replace(TRAILING_SLASH_RE, "")}${safePath}`;
34
+ }
35
+ const robots = seo.noIndex ? "noindex, nofollow" : null;
36
+ return {
37
+ title: fullTitle,
38
+ description,
39
+ ogTitle: pageTitle || fullTitle,
40
+ ogDescription: description,
41
+ ogImage,
42
+ canonical,
43
+ robots
44
+ };
45
+ }
46
+ /**
47
+ * Extract SEO data from a content item.
48
+ *
49
+ * Convenience accessor for the raw SEO fields without template resolution.
50
+ *
51
+ * @param content - The content item
52
+ * @returns The content's SEO fields
53
+ */
54
+ function getContentSeo(content) {
55
+ return content.seo ?? content.data.seo;
56
+ }
57
+ /**
58
+ * Build a media URL from a media reference ID.
59
+ * If it's already an absolute URL, return as-is.
60
+ */
61
+ function buildMediaUrl(imageRef, siteUrl) {
62
+ if (ABSOLUTE_URL_RE.test(imageRef)) return imageRef;
63
+ const mediaPath = `/_dineway/api/media/file/${imageRef}`;
64
+ if (siteUrl) return `${siteUrl.replace(TRAILING_SLASH_RE, "")}${mediaPath}`;
65
+ return mediaPath;
66
+ }
67
+
68
+ //#endregion
69
+ export { getContentSeo, getSeoMeta };
@@ -0,0 +1,38 @@
1
+ import { a as ListOptions, d as Storage, l as SignedUploadOptions, o as ListResult, p as UploadResult, r as DownloadResult, s as LocalStorageConfig, u as SignedUploadUrl } from "../types-ju-_ORz7.mjs";
2
+
3
+ //#region src/storage/local.d.ts
4
+ /**
5
+ * Local filesystem storage implementation
6
+ */
7
+ declare class LocalStorage implements Storage {
8
+ /** Resolved absolute base directory for all stored files */
9
+ private directory;
10
+ private baseUrl;
11
+ constructor(config: LocalStorageConfig);
12
+ /**
13
+ * Resolve a storage key to an absolute file path, ensuring it stays
14
+ * within the configured storage directory. Uses path.resolve() for
15
+ * canonical resolution rather than regex stripping.
16
+ *
17
+ * @throws DinewayStorageError if the resolved path escapes the base directory
18
+ */
19
+ private getFilePath;
20
+ upload(options: {
21
+ key: string;
22
+ body: Buffer | Uint8Array | ReadableStream<Uint8Array>;
23
+ contentType: string;
24
+ }): Promise<UploadResult>;
25
+ download(key: string): Promise<DownloadResult>;
26
+ delete(key: string): Promise<void>;
27
+ exists(key: string): Promise<boolean>;
28
+ list(options?: ListOptions): Promise<ListResult>;
29
+ getSignedUploadUrl(_options: SignedUploadOptions): Promise<SignedUploadUrl>;
30
+ getPublicUrl(key: string): string;
31
+ }
32
+ /**
33
+ * Create local storage adapter
34
+ * This is the factory function called at runtime
35
+ */
36
+ declare function createStorage(config: Record<string, unknown>): Storage;
37
+ //#endregion
38
+ export { LocalStorage, createStorage };
@@ -0,0 +1,165 @@
1
+ import { t as DinewayStorageError } from "../types-DShnjzb6.mjs";
2
+ import { createReadStream, existsSync } from "node:fs";
3
+ import * as path from "node:path";
4
+ import mime from "mime/lite";
5
+ import * as fs from "node:fs/promises";
6
+ import { Readable } from "node:stream";
7
+
8
+ //#region src/storage/local.ts
9
+ /**
10
+ * Local Filesystem Storage Implementation
11
+ *
12
+ * For development and testing. Stores files in a local directory.
13
+ */
14
+ /** Type guard for Node.js ErrnoException */
15
+ function isNodeError(error) {
16
+ return error instanceof Error && "code" in error;
17
+ }
18
+ /** Pattern to remove leading slashes */
19
+ const LEADING_SLASH_PATTERN = /^\//;
20
+ /** Pattern to remove trailing slashes */
21
+ const TRAILING_SLASH_PATTERN = /\/$/;
22
+ /**
23
+ * Local filesystem storage implementation
24
+ */
25
+ var LocalStorage = class {
26
+ /** Resolved absolute base directory for all stored files */
27
+ directory;
28
+ baseUrl;
29
+ constructor(config) {
30
+ this.directory = path.resolve(config.directory);
31
+ this.baseUrl = config.baseUrl.replace(TRAILING_SLASH_PATTERN, "");
32
+ }
33
+ /**
34
+ * Resolve a storage key to an absolute file path, ensuring it stays
35
+ * within the configured storage directory. Uses path.resolve() for
36
+ * canonical resolution rather than regex stripping.
37
+ *
38
+ * @throws DinewayStorageError if the resolved path escapes the base directory
39
+ */
40
+ getFilePath(key) {
41
+ const normalizedKey = key.replace(LEADING_SLASH_PATTERN, "");
42
+ const resolved = path.resolve(this.directory, normalizedKey);
43
+ if (!resolved.startsWith(this.directory + path.sep) && resolved !== this.directory) throw new DinewayStorageError("Invalid file path", "INVALID_PATH");
44
+ return resolved;
45
+ }
46
+ async upload(options) {
47
+ try {
48
+ const filePath = this.getFilePath(options.key);
49
+ const dir = path.dirname(filePath);
50
+ await fs.mkdir(dir, { recursive: true });
51
+ let buffer;
52
+ if (options.body instanceof ReadableStream) {
53
+ const chunks = [];
54
+ const reader = options.body.getReader();
55
+ while (true) {
56
+ const { done, value } = await reader.read();
57
+ if (done) break;
58
+ chunks.push(value);
59
+ }
60
+ buffer = Buffer.concat(chunks);
61
+ } else if (options.body instanceof Uint8Array) buffer = Buffer.from(options.body);
62
+ else buffer = options.body;
63
+ await fs.writeFile(filePath, buffer);
64
+ return {
65
+ key: options.key,
66
+ url: this.getPublicUrl(options.key),
67
+ size: buffer.length
68
+ };
69
+ } catch (error) {
70
+ throw new DinewayStorageError(`Failed to upload file: ${options.key}`, "UPLOAD_FAILED", error);
71
+ }
72
+ }
73
+ async download(key) {
74
+ try {
75
+ const filePath = this.getFilePath(key);
76
+ if (!existsSync(filePath)) throw new DinewayStorageError(`File not found: ${key}`, "NOT_FOUND");
77
+ const stat = await fs.stat(filePath);
78
+ const nodeStream = createReadStream(filePath);
79
+ return {
80
+ body: Readable.toWeb(nodeStream),
81
+ contentType: getContentType(path.extname(key).toLowerCase()),
82
+ size: stat.size
83
+ };
84
+ } catch (error) {
85
+ if (error instanceof DinewayStorageError) throw error;
86
+ throw new DinewayStorageError(`Failed to download file: ${key}`, "DOWNLOAD_FAILED", error);
87
+ }
88
+ }
89
+ async delete(key) {
90
+ try {
91
+ const filePath = this.getFilePath(key);
92
+ await fs.unlink(filePath);
93
+ } catch (error) {
94
+ if (!isNodeError(error) || error.code !== "ENOENT") throw new DinewayStorageError(`Failed to delete file: ${key}`, "DELETE_FAILED", error);
95
+ }
96
+ }
97
+ async exists(key) {
98
+ try {
99
+ const filePath = this.getFilePath(key);
100
+ await fs.access(filePath);
101
+ return true;
102
+ } catch {
103
+ return false;
104
+ }
105
+ }
106
+ async list(options = {}) {
107
+ try {
108
+ const prefix = options.prefix || "";
109
+ const searchDir = path.resolve(this.directory, path.dirname(prefix));
110
+ if (!searchDir.startsWith(this.directory + path.sep) && searchDir !== this.directory) throw new DinewayStorageError("Invalid list prefix", "INVALID_PATH");
111
+ const prefixBase = path.basename(prefix);
112
+ try {
113
+ await fs.access(searchDir);
114
+ } catch {
115
+ return { files: [] };
116
+ }
117
+ const entries = await fs.readdir(searchDir, { withFileTypes: true });
118
+ const files = [];
119
+ for (const entry of entries) if (entry.isFile() && entry.name.startsWith(prefixBase)) {
120
+ const key = path.join(path.dirname(prefix), entry.name);
121
+ const filePath = path.join(searchDir, entry.name);
122
+ const stat = await fs.stat(filePath);
123
+ files.push({
124
+ key,
125
+ size: stat.size,
126
+ lastModified: stat.mtime
127
+ });
128
+ }
129
+ files.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
130
+ const startIndex = options.cursor ? parseInt(options.cursor, 10) : 0;
131
+ const limit = options.limit || 1e3;
132
+ return {
133
+ files: files.slice(startIndex, startIndex + limit),
134
+ nextCursor: startIndex + limit < files.length ? String(startIndex + limit) : void 0
135
+ };
136
+ } catch (error) {
137
+ throw new DinewayStorageError("Failed to list files", "LIST_FAILED", error);
138
+ }
139
+ }
140
+ async getSignedUploadUrl(_options) {
141
+ throw new DinewayStorageError("Local storage does not support signed upload URLs. Upload files directly through the API.", "NOT_SUPPORTED");
142
+ }
143
+ getPublicUrl(key) {
144
+ return `${this.baseUrl}/${key}`;
145
+ }
146
+ };
147
+ /**
148
+ * Get content type from file extension
149
+ */
150
+ function getContentType(ext) {
151
+ return mime.getType(ext) ?? "application/octet-stream";
152
+ }
153
+ /**
154
+ * Create local storage adapter
155
+ * This is the factory function called at runtime
156
+ */
157
+ function createStorage(config) {
158
+ return new LocalStorage({
159
+ directory: typeof config.directory === "string" ? config.directory : "",
160
+ baseUrl: typeof config.baseUrl === "string" ? config.baseUrl : ""
161
+ });
162
+ }
163
+
164
+ //#endregion
165
+ export { LocalStorage, createStorage };
@@ -0,0 +1,31 @@
1
+ import { a as ListOptions, c as S3StorageConfig, d as Storage, l as SignedUploadOptions, o as ListResult, p as UploadResult, r as DownloadResult, u as SignedUploadUrl } from "../types-ju-_ORz7.mjs";
2
+
3
+ //#region src/storage/s3.d.ts
4
+ /**
5
+ * S3-compatible storage implementation
6
+ */
7
+ declare class S3Storage implements Storage {
8
+ private client;
9
+ private bucket;
10
+ private publicUrl?;
11
+ private endpoint;
12
+ constructor(config: S3StorageConfig);
13
+ upload(options: {
14
+ key: string;
15
+ body: Buffer | Uint8Array | ReadableStream<Uint8Array>;
16
+ contentType: string;
17
+ }): Promise<UploadResult>;
18
+ download(key: string): Promise<DownloadResult>;
19
+ delete(key: string): Promise<void>;
20
+ exists(key: string): Promise<boolean>;
21
+ list(options?: ListOptions): Promise<ListResult>;
22
+ getSignedUploadUrl(options: SignedUploadOptions): Promise<SignedUploadUrl>;
23
+ getPublicUrl(key: string): string;
24
+ }
25
+ /**
26
+ * Create S3 storage adapter
27
+ * This is the factory function called at runtime
28
+ */
29
+ declare function createStorage(config: Record<string, unknown>): Storage;
30
+ //#endregion
31
+ export { S3Storage, createStorage };
@@ -0,0 +1,174 @@
1
+ import { t as DinewayStorageError } from "../types-DShnjzb6.mjs";
2
+ import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
3
+ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
4
+
5
+ //#region src/storage/s3.ts
6
+ /**
7
+ * S3-Compatible Storage Implementation
8
+ *
9
+ * Uses the AWS SDK v3 for S3 operations.
10
+ * Works with AWS S3, Cloudflare R2, Minio, and other S3-compatible services.
11
+ */
12
+ const TRAILING_SLASH_PATTERN = /\/$/;
13
+ /** Type guard for AWS SDK errors (have a `name` property) */
14
+ function hasErrorName(error) {
15
+ return error instanceof Error && typeof error.name === "string";
16
+ }
17
+ /**
18
+ * S3-compatible storage implementation
19
+ */
20
+ var S3Storage = class {
21
+ client;
22
+ bucket;
23
+ publicUrl;
24
+ endpoint;
25
+ constructor(config) {
26
+ this.bucket = config.bucket;
27
+ this.publicUrl = config.publicUrl;
28
+ this.endpoint = config.endpoint;
29
+ this.client = new S3Client({
30
+ endpoint: config.endpoint,
31
+ region: config.region || "auto",
32
+ credentials: {
33
+ accessKeyId: config.accessKeyId,
34
+ secretAccessKey: config.secretAccessKey
35
+ },
36
+ forcePathStyle: true
37
+ });
38
+ }
39
+ async upload(options) {
40
+ try {
41
+ let body;
42
+ if (options.body instanceof ReadableStream) {
43
+ const chunks = [];
44
+ const reader = options.body.getReader();
45
+ while (true) {
46
+ const { done, value } = await reader.read();
47
+ if (done) break;
48
+ chunks.push(value);
49
+ }
50
+ body = Buffer.concat(chunks);
51
+ } else body = options.body;
52
+ await this.client.send(new PutObjectCommand({
53
+ Bucket: this.bucket,
54
+ Key: options.key,
55
+ Body: body,
56
+ ContentType: options.contentType
57
+ }));
58
+ return {
59
+ key: options.key,
60
+ url: this.getPublicUrl(options.key),
61
+ size: body.length
62
+ };
63
+ } catch (error) {
64
+ throw new DinewayStorageError(`Failed to upload file: ${options.key}`, "UPLOAD_FAILED", error);
65
+ }
66
+ }
67
+ async download(key) {
68
+ try {
69
+ const response = await this.client.send(new GetObjectCommand({
70
+ Bucket: this.bucket,
71
+ Key: key
72
+ }));
73
+ if (!response.Body) throw new DinewayStorageError(`File not found: ${key}`, "NOT_FOUND");
74
+ return {
75
+ body: response.Body.transformToWebStream(),
76
+ contentType: response.ContentType || "application/octet-stream",
77
+ size: response.ContentLength || 0
78
+ };
79
+ } catch (error) {
80
+ if (error instanceof DinewayStorageError || hasErrorName(error) && error.name === "NoSuchKey") throw new DinewayStorageError(`File not found: ${key}`, "NOT_FOUND", error);
81
+ throw new DinewayStorageError(`Failed to download file: ${key}`, "DOWNLOAD_FAILED", error);
82
+ }
83
+ }
84
+ async delete(key) {
85
+ try {
86
+ await this.client.send(new DeleteObjectCommand({
87
+ Bucket: this.bucket,
88
+ Key: key
89
+ }));
90
+ } catch (error) {
91
+ if (!hasErrorName(error) || error.name !== "NoSuchKey") throw new DinewayStorageError(`Failed to delete file: ${key}`, "DELETE_FAILED", error);
92
+ }
93
+ }
94
+ async exists(key) {
95
+ try {
96
+ await this.client.send(new HeadObjectCommand({
97
+ Bucket: this.bucket,
98
+ Key: key
99
+ }));
100
+ return true;
101
+ } catch (error) {
102
+ if (hasErrorName(error) && error.name === "NotFound") return false;
103
+ throw new DinewayStorageError(`Failed to check file existence: ${key}`, "HEAD_FAILED", error);
104
+ }
105
+ }
106
+ async list(options = {}) {
107
+ try {
108
+ const response = await this.client.send(new ListObjectsV2Command({
109
+ Bucket: this.bucket,
110
+ Prefix: options.prefix,
111
+ MaxKeys: options.limit,
112
+ ContinuationToken: options.cursor
113
+ }));
114
+ return {
115
+ files: (response.Contents || []).map((item) => ({
116
+ key: item.Key,
117
+ size: item.Size || 0,
118
+ lastModified: item.LastModified || /* @__PURE__ */ new Date(),
119
+ etag: item.ETag
120
+ })),
121
+ nextCursor: response.NextContinuationToken
122
+ };
123
+ } catch (error) {
124
+ throw new DinewayStorageError("Failed to list files", "LIST_FAILED", error);
125
+ }
126
+ }
127
+ async getSignedUploadUrl(options) {
128
+ try {
129
+ const expiresIn = options.expiresIn || 3600;
130
+ const command = new PutObjectCommand({
131
+ Bucket: this.bucket,
132
+ Key: options.key,
133
+ ContentType: options.contentType,
134
+ ContentLength: options.size
135
+ });
136
+ const url = await getSignedUrl(this.client, command, { expiresIn });
137
+ const expiresAt = new Date(Date.now() + expiresIn * 1e3).toISOString();
138
+ return {
139
+ url,
140
+ method: "PUT",
141
+ headers: {
142
+ "Content-Type": options.contentType,
143
+ ...options.size ? { "Content-Length": String(options.size) } : {}
144
+ },
145
+ expiresAt
146
+ };
147
+ } catch (error) {
148
+ throw new DinewayStorageError(`Failed to generate signed URL for: ${options.key}`, "SIGNED_URL_FAILED", error);
149
+ }
150
+ }
151
+ getPublicUrl(key) {
152
+ if (this.publicUrl) return `${this.publicUrl.replace(TRAILING_SLASH_PATTERN, "")}/${key}`;
153
+ return `${this.endpoint.replace(TRAILING_SLASH_PATTERN, "")}/${this.bucket}/${key}`;
154
+ }
155
+ };
156
+ /**
157
+ * Create S3 storage adapter
158
+ * This is the factory function called at runtime
159
+ */
160
+ function createStorage(config) {
161
+ const { endpoint, bucket, accessKeyId, secretAccessKey, region, publicUrl } = config;
162
+ if (typeof endpoint !== "string" || typeof bucket !== "string" || typeof accessKeyId !== "string" || typeof secretAccessKey !== "string") throw new Error("S3Storage requires 'endpoint', 'bucket', 'accessKeyId', and 'secretAccessKey' string config values");
163
+ return new S3Storage({
164
+ endpoint,
165
+ bucket,
166
+ accessKeyId,
167
+ secretAccessKey,
168
+ region: typeof region === "string" ? region : void 0,
169
+ publicUrl: typeof publicUrl === "string" ? publicUrl : void 0
170
+ });
171
+ }
172
+
173
+ //#endregion
174
+ export { S3Storage, createStorage };