mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-09 07:03:25 +00:00
refactor: optimize favicon fetcher with cache
This commit is contained in:
@@ -1,10 +1,8 @@
|
|||||||
|
import FaviconNotFoundException from '#exceptions/favicon_not_found_exception';
|
||||||
import type { HttpContext } from '@adonisjs/core/http';
|
import type { HttpContext } from '@adonisjs/core/http';
|
||||||
import logger from '@adonisjs/core/services/logger';
|
import logger from '@adonisjs/core/services/logger';
|
||||||
import { parse } from 'node-html-parser';
|
import { parse } from 'node-html-parser';
|
||||||
import { createReadStream } from 'node:fs';
|
import { cache } from '../lib/cache.js';
|
||||||
import { resolve } from 'node:path';
|
|
||||||
|
|
||||||
const LOG_PREFIX = '[Favicon]';
|
|
||||||
|
|
||||||
interface Favicon {
|
interface Favicon {
|
||||||
buffer: Buffer;
|
buffer: Buffer;
|
||||||
@@ -33,165 +31,123 @@ export default class FaviconsController {
|
|||||||
throw new Error('Missing URL');
|
throw new Error('Missing URL');
|
||||||
}
|
}
|
||||||
|
|
||||||
const faviconRequestUrl = this.buildFaviconUrl(url, '/favicon.ico');
|
const cacheNs = cache.namespace('favicon');
|
||||||
|
const favicon = await cacheNs.getOrSet({
|
||||||
|
key: url,
|
||||||
|
ttl: '1h',
|
||||||
|
factory: () => this.tryGetFavicon(url),
|
||||||
|
});
|
||||||
|
return this.sendImage(ctx, favicon);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async tryGetFavicon(url: string): Promise<Favicon> {
|
||||||
|
const faviconUrl = this.buildFaviconUrl(url, '/favicon.ico');
|
||||||
try {
|
try {
|
||||||
const favicon = await this.getFavicon(faviconRequestUrl);
|
return await this.fetchFavicon(faviconUrl);
|
||||||
return this.sendImage(ctx, favicon);
|
} catch {
|
||||||
} catch (error) {
|
logger.debug(`Unable to retrieve favicon from ${faviconUrl}`);
|
||||||
logger.debug(
|
|
||||||
`${LOG_PREFIX} [first: ${faviconRequestUrl}] unable to retrieve favicon from favicon.ico url`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestDocument = await this.makeRequestWithUserAgent(url);
|
const documentText = await this.fetchDocumentText(url);
|
||||||
const documentAsText = await requestDocument.text();
|
const faviconPath = this.extractFaviconPath(documentText);
|
||||||
|
|
||||||
const faviconPath = this.findFaviconPath(documentAsText);
|
|
||||||
if (!faviconPath) {
|
if (!faviconPath) {
|
||||||
logger.debug(
|
throw new FaviconNotFoundException(`No favicon path found in ${url}`);
|
||||||
`${LOG_PREFIX} [first: ${faviconRequestUrl}] no link/href attribute found`
|
|
||||||
);
|
|
||||||
return this.sendDefaultImage(ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalUrl = this.buildFaviconUrl(requestDocument.url, faviconPath);
|
return this.fetchFaviconFromPath(url, faviconPath);
|
||||||
try {
|
|
||||||
if (!faviconPath) {
|
|
||||||
throw new Error('Unable to find favicon path');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isBase64Image(faviconPath)) {
|
|
||||||
logger.debug(
|
|
||||||
`${LOG_PREFIX} [second: ${faviconRequestUrl}] info: base64, convert it to buffer`
|
|
||||||
);
|
|
||||||
const buffer = this.convertBase64ToBuffer(faviconPath);
|
|
||||||
return this.sendImage(ctx, {
|
|
||||||
buffer,
|
|
||||||
type: 'image/x-icon',
|
|
||||||
size: buffer.length,
|
|
||||||
url: faviconPath,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
||||||
const finalUrl = faviconPath.startsWith('http')
|
|
||||||
? faviconPath
|
|
||||||
: this.buildFaviconUrl(requestDocument.url, faviconPath);
|
|
||||||
const favicon = await this.downloadImageFromUrl(finalUrl);
|
|
||||||
if (!this.isImage(favicon.type)) {
|
|
||||||
throw new Error('Favicon path does not return an image');
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(`${LOG_PREFIX} [second: ${finalUrl}] success: image found`);
|
|
||||||
return this.sendImage(ctx, favicon);
|
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = error?.message || 'Unable to retrieve favicon';
|
|
||||||
logger.debug(`${LOG_PREFIX} [second: ${finalUrl}] error`, {
|
|
||||||
errorMessage,
|
|
||||||
});
|
|
||||||
return this.sendDefaultImage(ctx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildFaviconUrl(url: string, faviconPath: string) {
|
private async fetchFavicon(url: string): Promise<Favicon> {
|
||||||
const { origin } = new URL(url);
|
const response = await this.fetchWithUserAgent(url);
|
||||||
if (faviconPath.startsWith('/')) {
|
if (!response.ok) {
|
||||||
// https://example.com + /favicon.ico
|
throw new FaviconNotFoundException(`Request to ${url} failed`);
|
||||||
return origin + faviconPath;
|
|
||||||
}
|
|
||||||
// https://example.com/a/b?c=d -> https://example.com/a/b
|
|
||||||
const slimUrl = this.urlWithoutSearchParams(url);
|
|
||||||
|
|
||||||
// https://example.com/a/b/ -> https://example.com/a/b
|
|
||||||
const newUrl = slimUrl.endsWith('/') ? slimUrl.slice(0, -1) : slimUrl;
|
|
||||||
if (newUrl === origin) {
|
|
||||||
return `${newUrl}/${faviconPath}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://example.com/a/b or https://example.com/a/b/cdef -> https://example.com/a/
|
const blob = await response.blob();
|
||||||
const relativeUrl = this.removeLastSectionUrl(newUrl) + '/';
|
if (!this.isImage(blob.type) || blob.size === 0) {
|
||||||
if (relativeUrl.endsWith('/')) {
|
throw new FaviconNotFoundException(`Invalid image at ${url}`);
|
||||||
return relativeUrl + faviconPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://example.com/a -> https://example.com/a/favicon.ico
|
|
||||||
return `${relativeUrl}/${faviconPath}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private urlWithoutSearchParams(url: string) {
|
|
||||||
const newUrl = new URL(url);
|
|
||||||
return newUrl.protocol + '//' + newUrl.host + newUrl.pathname;
|
|
||||||
}
|
|
||||||
|
|
||||||
private removeLastSectionUrl(url: string) {
|
|
||||||
const urlArr = url.split('/');
|
|
||||||
urlArr.pop();
|
|
||||||
return urlArr.join('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
private findFaviconPath(text: string) {
|
|
||||||
const document = parse(text);
|
|
||||||
const favicon = Array.from(document.getElementsByTagName('link')).find(
|
|
||||||
(element) =>
|
|
||||||
element &&
|
|
||||||
this.relList.includes(element.getAttribute('rel')!) &&
|
|
||||||
element.getAttribute('href')
|
|
||||||
);
|
|
||||||
|
|
||||||
return favicon?.getAttribute('href') || undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getFavicon(url: string): Promise<Favicon> {
|
|
||||||
if (!url) throw new Error('Missing URL');
|
|
||||||
|
|
||||||
const favicon = await this.downloadImageFromUrl(url);
|
|
||||||
if (!this.isImage(favicon.type) || favicon.size === 0) {
|
|
||||||
throw new Error('Favicon path does not return an image');
|
|
||||||
}
|
|
||||||
|
|
||||||
return favicon;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async makeRequestWithUserAgent(url: string) {
|
|
||||||
const headers = new Headers();
|
|
||||||
headers.set('User-Agent', this.userAgent);
|
|
||||||
|
|
||||||
return await fetch(url, { headers });
|
|
||||||
}
|
|
||||||
|
|
||||||
private async downloadImageFromUrl(url: string): Promise<Favicon> {
|
|
||||||
const request = await this.makeRequestWithUserAgent(url);
|
|
||||||
if (!request.ok) {
|
|
||||||
throw new Error('Request failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
const blob = await request.blob();
|
|
||||||
return {
|
return {
|
||||||
buffer: Buffer.from(await blob.arrayBuffer()),
|
buffer: Buffer.from(await blob.arrayBuffer()),
|
||||||
url: request.url,
|
url: response.url,
|
||||||
type: blob.type,
|
type: blob.type,
|
||||||
size: blob.size,
|
size: blob.size,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private isImage = (type: string) => type.includes('image');
|
private async fetchDocumentText(url: string): Promise<string> {
|
||||||
|
const response = await this.fetchWithUserAgent(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new FaviconNotFoundException(`Request to ${url} failed`);
|
||||||
|
}
|
||||||
|
|
||||||
private isBase64Image = (data: string) => data.startsWith('data:image/');
|
return await response.text();
|
||||||
|
}
|
||||||
|
|
||||||
private convertBase64ToBuffer = (base64: string) =>
|
private extractFaviconPath(html: string): string | undefined {
|
||||||
Buffer.from(base64, 'base64');
|
const document = parse(html);
|
||||||
|
const link = document
|
||||||
|
.getElementsByTagName('link')
|
||||||
|
.find((element) => this.relList.includes(element.getAttribute('rel')!));
|
||||||
|
return link?.getAttribute('href');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fetchFaviconFromPath(
|
||||||
|
baseUrl: string,
|
||||||
|
path: string
|
||||||
|
): Promise<Favicon> {
|
||||||
|
if (this.isBase64Image(path)) {
|
||||||
|
const buffer = this.convertBase64ToBuffer(path);
|
||||||
|
return {
|
||||||
|
buffer,
|
||||||
|
type: 'image/x-icon',
|
||||||
|
size: buffer.length,
|
||||||
|
url: path,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const faviconUrl = this.buildFaviconUrl(baseUrl, path);
|
||||||
|
return this.fetchFavicon(faviconUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildFaviconUrl(base: string, path: string): string {
|
||||||
|
const { origin } = new URL(base);
|
||||||
|
if (path.startsWith('/')) {
|
||||||
|
return origin + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
const basePath = this.urlWithoutSearchParams(base);
|
||||||
|
const baseUrl = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;
|
||||||
|
return `${baseUrl}/${path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private urlWithoutSearchParams(url: string): string {
|
||||||
|
const { protocol, host, pathname } = new URL(url);
|
||||||
|
return `${protocol}//${host}${pathname}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private isImage(type: string): boolean {
|
||||||
|
return type.startsWith('image/');
|
||||||
|
}
|
||||||
|
|
||||||
|
private isBase64Image(data: string): boolean {
|
||||||
|
return data.startsWith('data:image/');
|
||||||
|
}
|
||||||
|
|
||||||
|
private convertBase64ToBuffer(base64: string): Buffer {
|
||||||
|
return Buffer.from(base64.split(',')[1], 'base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fetchWithUserAgent(url: string): Promise<Response> {
|
||||||
|
const headers = new Headers({ 'User-Agent': this.userAgent });
|
||||||
|
return fetch(url, { headers });
|
||||||
|
}
|
||||||
|
|
||||||
private sendImage(ctx: HttpContext, { buffer, type, size }: Favicon) {
|
private sendImage(ctx: HttpContext, { buffer, type, size }: Favicon) {
|
||||||
ctx.response.header('Content-Type', type);
|
ctx.response.header('Content-Type', type);
|
||||||
ctx.response.header('Content-Length', size);
|
ctx.response.header('Content-Length', size.toString());
|
||||||
ctx.response.send(buffer, true);
|
ctx.response.send(buffer, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sendDefaultImage(ctx: HttpContext) {
|
|
||||||
const readStream = createReadStream(
|
|
||||||
resolve(process.cwd(), './public/empty-image.png')
|
|
||||||
);
|
|
||||||
ctx.response.writeHead(206);
|
|
||||||
ctx.response.stream(readStream);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
20
app/exceptions/favicon_not_found_exception.ts
Normal file
20
app/exceptions/favicon_not_found_exception.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Exception } from '@adonisjs/core/exceptions';
|
||||||
|
import { HttpContext } from '@adonisjs/core/http';
|
||||||
|
import logger from '@adonisjs/core/services/logger';
|
||||||
|
import { createReadStream } from 'node:fs';
|
||||||
|
import { resolve } from 'node:path';
|
||||||
|
|
||||||
|
export default class FaviconNotFoundException extends Exception {
|
||||||
|
static status = 404;
|
||||||
|
static code = 'E_FAVICON_NOT_FOUND';
|
||||||
|
|
||||||
|
async handle(error: this, ctx: HttpContext) {
|
||||||
|
const readStream = createReadStream(
|
||||||
|
resolve(process.cwd(), './public/empty-image.png')
|
||||||
|
);
|
||||||
|
|
||||||
|
ctx.response.header('Content-Type', 'image/png');
|
||||||
|
ctx.response.stream(readStream);
|
||||||
|
logger.debug(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
app/lib/cache.ts
Normal file
10
app/lib/cache.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { BentoCache, bentostore } from 'bentocache';
|
||||||
|
import { memoryDriver } from 'bentocache/drivers/memory';
|
||||||
|
|
||||||
|
export const cache = new BentoCache({
|
||||||
|
default: 'cache',
|
||||||
|
|
||||||
|
stores: {
|
||||||
|
cache: bentostore().useL1Layer(memoryDriver({ maxSize: 10_000 })),
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -28,10 +28,10 @@ const ItemLegeng = styled.span(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
interface CommonResultProps {
|
interface CommonResultProps {
|
||||||
isActive: boolean;
|
|
||||||
innerRef: RefObject<HTMLLIElement>;
|
innerRef: RefObject<HTMLLIElement>;
|
||||||
onHoverEnter: () => void;
|
isActive: boolean;
|
||||||
onHoverLeave: () => void;
|
onMouseEnter: () => void;
|
||||||
|
onMouseLeave: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SearchResultItem({
|
export default function SearchResultItem({
|
||||||
@@ -46,14 +46,13 @@ export default function SearchResultItem({
|
|||||||
const itemRef = useRef<HTMLLIElement>(null);
|
const itemRef = useRef<HTMLLIElement>(null);
|
||||||
const [isHovering, setHovering] = useState<boolean>(false);
|
const [isHovering, setHovering] = useState<boolean>(false);
|
||||||
|
|
||||||
const handleHoverEnter = () => {
|
const onMouseEnter = () => {
|
||||||
if (!isHovering) {
|
if (!isHovering) {
|
||||||
onHover(result);
|
onHover(result);
|
||||||
setHovering(true);
|
setHovering(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const onMouseLeave = () => setHovering(false);
|
||||||
const handleHoverLeave = () => setHovering(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isActive && !isHovering) {
|
if (isActive && !isHovering) {
|
||||||
@@ -64,31 +63,22 @@ export default function SearchResultItem({
|
|||||||
}
|
}
|
||||||
}, [itemRef, isActive]);
|
}, [itemRef, isActive]);
|
||||||
|
|
||||||
|
const commonProps = {
|
||||||
|
onMouseEnter,
|
||||||
|
onMouseLeave,
|
||||||
|
isActive,
|
||||||
|
};
|
||||||
return result.type === 'collection' ? (
|
return result.type === 'collection' ? (
|
||||||
<ResultCollection
|
<ResultCollection result={result} innerRef={itemRef} {...commonProps} />
|
||||||
result={result}
|
|
||||||
isActive={isActive}
|
|
||||||
onHoverEnter={handleHoverEnter}
|
|
||||||
onHoverLeave={handleHoverLeave}
|
|
||||||
innerRef={itemRef}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<ResultLink
|
<ResultLink result={result} innerRef={itemRef} {...commonProps} />
|
||||||
result={result}
|
|
||||||
isActive={isActive}
|
|
||||||
onHoverEnter={handleHoverEnter}
|
|
||||||
onHoverLeave={handleHoverLeave}
|
|
||||||
innerRef={itemRef}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ResultLink({
|
function ResultLink({
|
||||||
result,
|
result,
|
||||||
isActive,
|
|
||||||
innerRef,
|
innerRef,
|
||||||
onHoverEnter,
|
...props
|
||||||
onHoverLeave,
|
|
||||||
}: {
|
}: {
|
||||||
result: SearchResultLink;
|
result: SearchResultLink;
|
||||||
} & CommonResultProps) {
|
} & CommonResultProps) {
|
||||||
@@ -100,11 +90,9 @@ function ResultLink({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SearchItemStyle
|
<SearchItemStyle
|
||||||
onMouseEnter={onHoverEnter}
|
|
||||||
onMouseLeave={onHoverLeave}
|
|
||||||
isActive={isActive}
|
|
||||||
key={result.type + result.id.toString()}
|
key={result.type + result.id.toString()}
|
||||||
ref={innerRef}
|
ref={innerRef}
|
||||||
|
{...props}
|
||||||
>
|
>
|
||||||
<LinkFavicon url={link.url} size={20} />
|
<LinkFavicon url={link.url} size={20} />
|
||||||
<TextEllipsis
|
<TextEllipsis
|
||||||
@@ -119,19 +107,15 @@ function ResultLink({
|
|||||||
|
|
||||||
const ResultCollection = ({
|
const ResultCollection = ({
|
||||||
result,
|
result,
|
||||||
isActive,
|
|
||||||
innerRef,
|
innerRef,
|
||||||
onHoverEnter,
|
...props
|
||||||
onHoverLeave,
|
|
||||||
}: {
|
}: {
|
||||||
result: SearchResultCollection;
|
result: SearchResultCollection;
|
||||||
} & CommonResultProps) => (
|
} & CommonResultProps) => (
|
||||||
<SearchItemStyle
|
<SearchItemStyle
|
||||||
onMouseEnter={onHoverEnter}
|
|
||||||
onMouseLeave={onHoverLeave}
|
|
||||||
isActive={isActive}
|
|
||||||
key={result.type + result.id.toString()}
|
key={result.type + result.id.toString()}
|
||||||
ref={innerRef}
|
ref={innerRef}
|
||||||
|
{...props}
|
||||||
>
|
>
|
||||||
<AiOutlineFolder size={24} />
|
<AiOutlineFolder size={24} />
|
||||||
<TextEllipsis
|
<TextEllipsis
|
||||||
|
|||||||
199
package-lock.json
generated
199
package-lock.json
generated
@@ -24,6 +24,7 @@
|
|||||||
"@inertiajs/react": "^1.0.16",
|
"@inertiajs/react": "^1.0.16",
|
||||||
"@izzyjs/route": "^1.1.0-0",
|
"@izzyjs/route": "^1.1.0-0",
|
||||||
"@vinejs/vine": "^2.0.0",
|
"@vinejs/vine": "^2.0.0",
|
||||||
|
"bentocache": "^1.0.0-beta.9",
|
||||||
"edge.js": "^6.0.2",
|
"edge.js": "^6.0.2",
|
||||||
"hex-rgb": "^5.0.0",
|
"hex-rgb": "^5.0.0",
|
||||||
"i18next": "^23.11.4",
|
"i18next": "^23.11.4",
|
||||||
@@ -421,25 +422,6 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@adonisjs/core/node_modules/@noble/hashes": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 16"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://paulmillr.com/funding/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@adonisjs/core/node_modules/@paralleldrive/cuid2": {
|
|
||||||
"version": "2.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz",
|
|
||||||
"integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==",
|
|
||||||
"dependencies": {
|
|
||||||
"@noble/hashes": "^1.1.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@adonisjs/core/node_modules/@phc/format": {
|
"node_modules/@adonisjs/core/node_modules/@phc/format": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz",
|
||||||
@@ -2798,6 +2780,27 @@
|
|||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@boringnode/bus": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@boringnode/bus/-/bus-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-i6AKHvFqRUC5KZTX7WXYcWIV8Wfnem6t5Li1Bn5oU+YXAq0zw1y09bZk9HxB2smugfcFlDgzRwpXuwC9fLTE9g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@paralleldrive/cuid2": "^2.2.2",
|
||||||
|
"@poppinss/utils": "^6.7.3",
|
||||||
|
"object-hash": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.11.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"ioredis": "^5.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"ioredis": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@cspotcode/source-map-support": {
|
"node_modules/@cspotcode/source-map-support": {
|
||||||
"version": "0.8.1",
|
"version": "0.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||||
@@ -3768,6 +3771,25 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@noble/hashes": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@paralleldrive/cuid2": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/hashes": "^1.1.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@poppinss/chokidar-ts": {
|
"node_modules/@poppinss/chokidar-ts": {
|
||||||
"version": "4.1.4",
|
"version": "4.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@poppinss/chokidar-ts/-/chokidar-ts-4.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@poppinss/chokidar-ts/-/chokidar-ts-4.1.4.tgz",
|
||||||
@@ -7427,6 +7449,14 @@
|
|||||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/async-mutex": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/babel-plugin-macros": {
|
"node_modules/babel-plugin-macros": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
|
||||||
@@ -7683,6 +7713,45 @@
|
|||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||||
},
|
},
|
||||||
|
"node_modules/bentocache": {
|
||||||
|
"version": "1.0.0-beta.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/bentocache/-/bentocache-1.0.0-beta.9.tgz",
|
||||||
|
"integrity": "sha512-KykJ/8q20DO9FHQ7Yhqv8RlgI/jo7kgoE0EJ65w30sziNdaDSsy8gVRCOTf06m+TcoVdBu2luKH9V4xaavBAbA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@boringnode/bus": "^0.6.0",
|
||||||
|
"@poppinss/utils": "^6.7.3",
|
||||||
|
"async-mutex": "^0.5.0",
|
||||||
|
"chunkify": "^5.0.0",
|
||||||
|
"hexoid": "^1.0.0",
|
||||||
|
"lru-cache": "^10.2.2",
|
||||||
|
"p-timeout": "^6.1.2",
|
||||||
|
"typescript-log": "^2.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@aws-sdk/client-dynamodb": "^3.438.0",
|
||||||
|
"ioredis": "^5.3.2",
|
||||||
|
"knex": "^3.0.1",
|
||||||
|
"kysely": "^0.27.3",
|
||||||
|
"orchid-orm": "^1.24.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@aws-sdk/client-dynamodb": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"ioredis": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"knex": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"kysely": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"orchid-orm": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/camelcase": {
|
"node_modules/camelcase": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz",
|
||||||
@@ -7936,6 +8005,17 @@
|
|||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chunkify": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chunkify/-/chunkify-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-G8dj/3/Gm+1yL4oWSdwIxihZWFlgC4V2zYtIApacI0iPIRKBHlBGOGAiDUBZgrj4H8MBA8g8fPFwnJrWF3wl7Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/classnames": {
|
"node_modules/classnames": {
|
||||||
"version": "2.5.1",
|
"version": "2.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||||
@@ -8163,18 +8243,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cpy/node_modules/p-timeout": {
|
|
||||||
"version": "6.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.2.tgz",
|
|
||||||
"integrity": "sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==",
|
|
||||||
"devOptional": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.16"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cpy/node_modules/path-type": {
|
"node_modules/cpy/node_modules/path-type": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||||
@@ -9113,12 +9181,6 @@
|
|||||||
"node": ">=12.20"
|
"node": ">=12.20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-jsonc/node_modules/tslib": {
|
|
||||||
"version": "2.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
|
||||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/eslint-plugin-prettier": {
|
"node_modules/eslint-plugin-prettier": {
|
||||||
"version": "5.1.3",
|
"version": "5.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz",
|
||||||
@@ -9195,12 +9257,6 @@
|
|||||||
"url": "https://opencollective.com/unts"
|
"url": "https://opencollective.com/unts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-prettier/node_modules/tslib": {
|
|
||||||
"version": "2.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
|
||||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/eslint-plugin-unicorn": {
|
"node_modules/eslint-plugin-unicorn": {
|
||||||
"version": "47.0.0",
|
"version": "47.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-47.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-47.0.0.tgz",
|
||||||
@@ -11282,6 +11338,14 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hexoid": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hoist-non-react-statics": {
|
"node_modules/hoist-non-react-statics": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
@@ -11975,6 +12039,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/lru-cache": {
|
||||||
|
"version": "10.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
|
||||||
|
"integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": "14 || >=16.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/luxon": {
|
"node_modules/luxon": {
|
||||||
"version": "3.4.4",
|
"version": "3.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz",
|
||||||
@@ -12082,6 +12154,14 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/object-hash": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/on-exit-leak-free": {
|
"node_modules/on-exit-leak-free": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
|
||||||
@@ -12090,6 +12170,17 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/p-timeout": {
|
||||||
|
"version": "6.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.2.tgz",
|
||||||
|
"integrity": "sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-parse": {
|
"node_modules/path-parse": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
@@ -12906,15 +12997,6 @@
|
|||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/read-package-up/node_modules/lru-cache": {
|
|
||||||
"version": "10.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
|
|
||||||
"integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": "14 || >=16.14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/read-package-up/node_modules/normalize-package-data": {
|
"node_modules/read-package-up/node_modules/normalize-package-data": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.1.tgz",
|
||||||
@@ -13903,6 +13985,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||||
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.4.5",
|
"version": "5.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
|
||||||
@@ -13916,6 +14003,14 @@
|
|||||||
"node": ">=14.17"
|
"node": ">=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typescript-log": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript-log/-/typescript-log-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-TyW8lmURJSo0yjBovEhESpah3haDYBgsnQRocBF4MQSDJSYs/DOmhjo2cpSrGyvD9OaX++dbbonq9TkIQeA+Bw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/v8-compile-cache-lib": {
|
"node_modules/v8-compile-cache-lib": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||||
|
|||||||
@@ -75,6 +75,7 @@
|
|||||||
"@inertiajs/react": "^1.0.16",
|
"@inertiajs/react": "^1.0.16",
|
||||||
"@izzyjs/route": "^1.1.0-0",
|
"@izzyjs/route": "^1.1.0-0",
|
||||||
"@vinejs/vine": "^2.0.0",
|
"@vinejs/vine": "^2.0.0",
|
||||||
|
"bentocache": "^1.0.0-beta.9",
|
||||||
"edge.js": "^6.0.2",
|
"edge.js": "^6.0.2",
|
||||||
"hex-rgb": "^5.0.0",
|
"hex-rgb": "^5.0.0",
|
||||||
"i18next": "^23.11.4",
|
"i18next": "^23.11.4",
|
||||||
|
|||||||
Reference in New Issue
Block a user