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.
- package/LICENSE +9 -0
- package/README.md +89 -0
- package/dist/adapters-BlzWJG82.d.mts +106 -0
- package/dist/apply-CAPvMfoU.mjs +1339 -0
- package/dist/astro/index.d.mts +50 -0
- package/dist/astro/index.mjs +1326 -0
- package/dist/astro/middleware/auth.d.mts +30 -0
- package/dist/astro/middleware/auth.mjs +708 -0
- package/dist/astro/middleware/redirect.d.mts +21 -0
- package/dist/astro/middleware/redirect.mjs +62 -0
- package/dist/astro/middleware/request-context.d.mts +17 -0
- package/dist/astro/middleware/request-context.mjs +1371 -0
- package/dist/astro/middleware/setup.d.mts +19 -0
- package/dist/astro/middleware/setup.mjs +46 -0
- package/dist/astro/middleware.d.mts +12 -0
- package/dist/astro/middleware.mjs +1716 -0
- package/dist/astro/types.d.mts +269 -0
- package/dist/astro/types.mjs +1 -0
- package/dist/base64-F8-DUraK.mjs +58 -0
- package/dist/byline-DeWCMU_i.mjs +234 -0
- package/dist/bylines-DyqBV9EQ.mjs +137 -0
- package/dist/chunk-ClPoSABd.mjs +21 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +3987 -0
- package/dist/client/external-auth-headers.d.mts +38 -0
- package/dist/client/external-auth-headers.mjs +101 -0
- package/dist/client/index.d.mts +397 -0
- package/dist/client/index.mjs +345 -0
- package/dist/config-Cq8H0SfX.mjs +46 -0
- package/dist/connection-C9pxzuag.mjs +52 -0
- package/dist/content-zSgdNmnt.mjs +836 -0
- package/dist/db/index.d.mts +4 -0
- package/dist/db/index.mjs +62 -0
- package/dist/db/libsql.d.mts +10 -0
- package/dist/db/libsql.mjs +21 -0
- package/dist/db/postgres.d.mts +10 -0
- package/dist/db/postgres.mjs +29 -0
- package/dist/db/sqlite.d.mts +10 -0
- package/dist/db/sqlite.mjs +15 -0
- package/dist/default-WYlzADZL.mjs +80 -0
- package/dist/dialect-helpers-B9uSp2GJ.mjs +89 -0
- package/dist/error-DrxtnGPg.mjs +26 -0
- package/dist/index-C-jx21qs.d.mts +4771 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.mjs +30 -0
- package/dist/load-C6FCD1FU.mjs +27 -0
- package/dist/loader-qKmo0wAY.mjs +446 -0
- package/dist/manifest-schema-CTSEyIJ3.mjs +186 -0
- package/dist/media/index.d.mts +25 -0
- package/dist/media/index.mjs +54 -0
- package/dist/media/local-runtime.d.mts +38 -0
- package/dist/media/local-runtime.mjs +132 -0
- package/dist/media-DMTr80Gv.mjs +199 -0
- package/dist/mode-BlyYtIFO.mjs +22 -0
- package/dist/page/index.d.mts +148 -0
- package/dist/page/index.mjs +419 -0
- package/dist/placeholder-B3knXwNc.mjs +267 -0
- package/dist/placeholder-bOx1xCTY.d.mts +283 -0
- package/dist/plugin-utils.d.mts +57 -0
- package/dist/plugin-utils.mjs +77 -0
- package/dist/plugins/adapt-sandbox-entry.d.mts +21 -0
- package/dist/plugins/adapt-sandbox-entry.mjs +112 -0
- package/dist/query-BiaPl_g2.mjs +459 -0
- package/dist/redirect-JPqLAbxa.mjs +328 -0
- package/dist/registry-DSd1GWB8.mjs +851 -0
- package/dist/request-context.d.mts +49 -0
- package/dist/request-context.mjs +42 -0
- package/dist/runner-B5l1JfOj.d.mts +26 -0
- package/dist/runner-BGUGywgG.mjs +1529 -0
- package/dist/runtime.d.mts +25 -0
- package/dist/runtime.mjs +41 -0
- package/dist/search-BNruJHDL.mjs +11054 -0
- package/dist/seed/index.d.mts +3 -0
- package/dist/seed/index.mjs +15 -0
- package/dist/seo/index.d.mts +69 -0
- package/dist/seo/index.mjs +69 -0
- package/dist/storage/local.d.mts +38 -0
- package/dist/storage/local.mjs +165 -0
- package/dist/storage/s3.d.mts +31 -0
- package/dist/storage/s3.mjs +174 -0
- package/dist/tokens-4vgYuXsZ.mjs +170 -0
- package/dist/transport-C5FYnid7.mjs +417 -0
- package/dist/transport-gIL-e43D.d.mts +41 -0
- package/dist/types-BawVha09.mjs +30 -0
- package/dist/types-BgQeVaPj.d.mts +192 -0
- package/dist/types-CLLdsG3g.d.mts +103 -0
- package/dist/types-D38djUXv.d.mts +1196 -0
- package/dist/types-DShnjzb6.mjs +15 -0
- package/dist/types-DkvMXalq.d.mts +425 -0
- package/dist/types-DuNbGKjF.mjs +74 -0
- package/dist/types-ju-_ORz7.d.mts +182 -0
- package/dist/validate-CXnRKfJK.mjs +327 -0
- package/dist/validate-CqRJb_xU.mjs +96 -0
- package/dist/validate-DVKJJ-M_.d.mts +377 -0
- package/locals.d.ts +47 -0
- 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 };
|