mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-17 21:06:04 +00:00
@@ -18,10 +18,10 @@ bknd simplifies app development by providing a fully functional backend for data
|
|||||||
> and therefore full backward compatibility is not guaranteed before reaching v1.0.0.
|
> and therefore full backward compatibility is not guaranteed before reaching v1.0.0.
|
||||||
|
|
||||||
## Size
|
## Size
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
The size on npm is misleading, as the `bknd` package includes the backend, the ui components as well as the whole backend bundled into the cli including static assets.
|
The size on npm is misleading, as the `bknd` package includes the backend, the ui components as well as the whole backend bundled into the cli including static assets.
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ describe("EventManager", async () => {
|
|||||||
emgr.onEvent(ReturnEvent, async () => "1", "sync");
|
emgr.onEvent(ReturnEvent, async () => "1", "sync");
|
||||||
emgr.onEvent(ReturnEvent, async () => "0", "sync");
|
emgr.onEvent(ReturnEvent, async () => "0", "sync");
|
||||||
|
|
||||||
|
// @todo: fix this
|
||||||
// @ts-expect-error must be string
|
// @ts-expect-error must be string
|
||||||
emgr.onEvent(ReturnEvent, async () => 0, "sync");
|
emgr.onEvent(ReturnEvent, async () => 0, "sync");
|
||||||
|
|
||||||
|
|||||||
@@ -34,4 +34,21 @@ describe("media/mime-types", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("isMimeType", () => {
|
||||||
|
expect(tiny.isMimeType("image/jpeg")).toBe(true);
|
||||||
|
expect(tiny.isMimeType("image/jpeg", ["image/png"])).toBe(true);
|
||||||
|
expect(tiny.isMimeType("image/png", ["image/png"])).toBe(false);
|
||||||
|
expect(tiny.isMimeType("image/png")).toBe(true);
|
||||||
|
expect(tiny.isMimeType("whatever")).toBe(false);
|
||||||
|
expect(tiny.isMimeType("text/tab-separated-values")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("extension", () => {
|
||||||
|
expect(tiny.extension("image/png")).toBe("png");
|
||||||
|
expect(tiny.extension("image/jpeg")).toBe("jpeg");
|
||||||
|
expect(tiny.extension("application/zip")).toBe("zip");
|
||||||
|
expect(tiny.extension("text/tab-separated-values")).toBe("tsv");
|
||||||
|
expect(tiny.extension("application/zip")).toBe("zip");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"bin": "./dist/cli/index.js",
|
"bin": "./dist/cli/index.js",
|
||||||
"version": "0.8.0",
|
"version": "0.8.1",
|
||||||
"description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, Remix, Astro, Cloudflare, Bun, Node, AWS Lambda & more.",
|
"description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, Remix, Astro, Cloudflare, Bun, Node, AWS Lambda & more.",
|
||||||
"homepage": "https://bknd.io",
|
"homepage": "https://bknd.io",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -22,9 +22,9 @@
|
|||||||
"build:cli": "bun build src/cli/index.ts --target node --outdir dist/cli --minify",
|
"build:cli": "bun build src/cli/index.ts --target node --outdir dist/cli --minify",
|
||||||
"build:static": "vite build",
|
"build:static": "vite build",
|
||||||
"watch": "bun run build.ts --types --watch",
|
"watch": "bun run build.ts --types --watch",
|
||||||
"types": "bun tsc --noEmit",
|
"types": "bun tsc -p tsconfig.build.json --noEmit",
|
||||||
"clean:types": "find ./dist -name '*.d.ts' -delete && rm -f ./dist/tsconfig.tsbuildinfo",
|
"clean:types": "find ./dist -name '*.d.ts' -delete && rm -f ./dist/tsconfig.tsbuildinfo",
|
||||||
"build:types": "tsc --emitDeclarationOnly && tsc-alias",
|
"build:types": "tsc -p tsconfig.build.json --emitDeclarationOnly && tsc-alias",
|
||||||
"updater": "bun x npm-check-updates -ui",
|
"updater": "bun x npm-check-updates -ui",
|
||||||
"cli": "LOCAL=1 bun src/cli/index.ts",
|
"cli": "LOCAL=1 bun src/cli/index.ts",
|
||||||
"prepublishOnly": "bun run types && bun run test && bun run build:all && cp ../README.md ./",
|
"prepublishOnly": "bun run types && bun run test && bun run build:all && cp ../README.md ./",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { Api, type ApiOptions } from "Api";
|
||||||
import type { CreateUserPayload } from "auth/AppAuth";
|
import type { CreateUserPayload } from "auth/AppAuth";
|
||||||
import { Api, type ApiOptions } from "bknd/client";
|
|
||||||
import { $console } from "core";
|
import { $console } from "core";
|
||||||
import { Event } from "core/events";
|
import { Event } from "core/events";
|
||||||
import { Connection, type LibSqlCredentials, LibsqlConnection } from "data";
|
import { Connection, type LibSqlCredentials, LibsqlConnection } from "data";
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ export class DataController extends Controller {
|
|||||||
return c.notFound();
|
return c.notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
const where = c.req.json() as any;
|
const where = (await c.req.json()) as any;
|
||||||
const result = await this.em.repository(entity).count(where);
|
const result = await this.em.repository(entity).count(where);
|
||||||
return c.json({ entity, count: result.count });
|
return c.json({ entity, count: result.count });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,8 +124,9 @@ export class AppMedia extends Module<typeof mediaConfigSchema> {
|
|||||||
const mutator = em.mutator(media);
|
const mutator = em.mutator(media);
|
||||||
mutator.__unstable_toggleSystemEntityCreation(false);
|
mutator.__unstable_toggleSystemEntityCreation(false);
|
||||||
const payload = this.uploadedEventDataToMediaPayload(e.params);
|
const payload = this.uploadedEventDataToMediaPayload(e.params);
|
||||||
await mutator.insertOne(payload);
|
const { data } = await mutator.insertOne(payload);
|
||||||
mutator.__unstable_toggleSystemEntityCreation(true);
|
mutator.__unstable_toggleSystemEntityCreation(true);
|
||||||
|
return { data };
|
||||||
},
|
},
|
||||||
{ mode: "sync", id: "add-data-media" }
|
{ mode: "sync", id: "add-data-media" }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ export class MediaApi extends ModuleApi<MediaApiOptions> {
|
|||||||
return (await res.blob()) as File;
|
return (await res.blob()) as File;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFileUploadUrl(file: FileWithPath): string {
|
getFileUploadUrl(file?: FileWithPath): string {
|
||||||
|
if (!file) return this.getUrl("/upload");
|
||||||
return this.getUrl(`/upload/${file.path}`);
|
return this.getUrl(`/upload/${file.path}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,11 +72,8 @@ export class MediaController extends Controller {
|
|||||||
|
|
||||||
// upload file
|
// upload file
|
||||||
// @todo: add required type for "upload endpoints"
|
// @todo: add required type for "upload endpoints"
|
||||||
hono.post("/upload/:filename", async (c) => {
|
hono.post("/upload/:filename?", async (c) => {
|
||||||
const { filename } = c.req.param();
|
const reqname = c.req.param("filename");
|
||||||
if (!filename) {
|
|
||||||
throw new Error("No file name provided");
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = await getFileFromContext(c);
|
const body = await getFileFromContext(c);
|
||||||
if (!body) {
|
if (!body) {
|
||||||
@@ -89,7 +86,9 @@ export class MediaController extends Controller {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filename = reqname ?? getRandomizedFilename(body as File);
|
||||||
const res = await this.getStorage().uploadFile(body, filename);
|
const res = await this.getStorage().uploadFile(body, filename);
|
||||||
|
|
||||||
return c.json(res, HttpStatus.CREATED);
|
return c.json(res, HttpStatus.CREATED);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -191,8 +190,8 @@ export class MediaController extends Controller {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const file_name = getRandomizedFilename(file as File);
|
const filename = getRandomizedFilename(file as File);
|
||||||
const info = await this.getStorage().uploadFile(file, file_name, true);
|
const info = await this.getStorage().uploadFile(file, filename, true);
|
||||||
|
|
||||||
const mutator = this.media.em.mutator(media_entity);
|
const mutator = this.media.em.mutator(media_entity);
|
||||||
mutator.__unstable_toggleSystemEntityCreation(false);
|
mutator.__unstable_toggleSystemEntityCreation(false);
|
||||||
|
|||||||
@@ -119,7 +119,10 @@ export class Storage implements EmitsEvents {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (!noEmit) {
|
if (!noEmit) {
|
||||||
await this.emgr.emit(new StorageEvents.FileUploadedEvent(eventData));
|
const result = await this.emgr.emit(new StorageEvents.FileUploadedEvent(eventData));
|
||||||
|
if (result.returned) {
|
||||||
|
return result.params;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return eventData;
|
return eventData;
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
import { Event } from "core/events";
|
import { Event, InvalidEventReturn } from "core/events";
|
||||||
import type { FileBody, FileUploadPayload } from "../Storage";
|
import type { FileBody, FileUploadPayload } from "../Storage";
|
||||||
|
|
||||||
export type FileUploadedEventData = FileUploadPayload & {
|
export type FileUploadedEventData = FileUploadPayload & {
|
||||||
file: FileBody;
|
file: FileBody;
|
||||||
};
|
};
|
||||||
export class FileUploadedEvent extends Event<FileUploadedEventData> {
|
export class FileUploadedEvent extends Event<FileUploadedEventData, object> {
|
||||||
static override slug = "file-uploaded";
|
static override slug = "file-uploaded";
|
||||||
|
|
||||||
|
override validate(data: object) {
|
||||||
|
if (typeof data !== "object") {
|
||||||
|
throw new InvalidEventReturn("object", typeof data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.clone({
|
||||||
|
// prepending result, so original is always kept
|
||||||
|
...data,
|
||||||
|
...this.params
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FileDeletedEvent extends Event<{ name: string }> {
|
export class FileDeletedEvent extends Event<{ name: string }> {
|
||||||
|
|||||||
@@ -77,6 +77,17 @@ export function guess(f: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isMimeType(mime: any, exclude: string[] = []) {
|
export function isMimeType(mime: any, exclude: string[] = []) {
|
||||||
|
if (exclude.includes(mime)) return false;
|
||||||
|
|
||||||
|
// try quick first
|
||||||
|
if (
|
||||||
|
Object.entries(Q)
|
||||||
|
.flatMap(([t, e]) => e.map((x) => `${t}/${x}`))
|
||||||
|
.includes(mime)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
for (const [k, v] of M.entries()) {
|
for (const [k, v] of M.entries()) {
|
||||||
if (v === mime && !exclude.includes(k)) {
|
if (v === mime && !exclude.includes(k)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -86,6 +97,14 @@ export function isMimeType(mime: any, exclude: string[] = []) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function extension(mime: string) {
|
export function extension(mime: string) {
|
||||||
|
for (const [t, e] of Object.entries(Q)) {
|
||||||
|
for (const _e of e) {
|
||||||
|
if (mime === `${t}/${_e}`) {
|
||||||
|
return _e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const [k, v] of M.entries()) {
|
for (const [k, v] of M.entries()) {
|
||||||
if (v === mime) {
|
if (v === mime) {
|
||||||
return k;
|
return k;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { randomString } from "core/utils";
|
import { isFile, randomString } from "core/utils";
|
||||||
|
import { extension } from "media/storage/mime-types-tiny";
|
||||||
|
|
||||||
export function getExtension(filename: string): string | undefined {
|
export function getExtensionFromName(filename: string): string | undefined {
|
||||||
if (!filename.includes(".")) return;
|
if (!filename.includes(".")) return;
|
||||||
|
|
||||||
const parts = filename.split(".");
|
const parts = filename.split(".");
|
||||||
@@ -17,6 +18,12 @@ export function getRandomizedFilename(file: File | string, length = 16): string
|
|||||||
throw new Error("Invalid file name");
|
throw new Error("Invalid file name");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let ext = getExtensionFromName(filename);
|
||||||
|
if (isFile(file) && file.type) {
|
||||||
|
const _ext = extension(file.type);
|
||||||
|
if (_ext.length > 0) ext = _ext;
|
||||||
|
}
|
||||||
|
|
||||||
// @todo: use uuid instead?
|
// @todo: use uuid instead?
|
||||||
return [randomString(length), getExtension(filename)].filter(Boolean).join(".");
|
return [randomString(length), ext].filter(Boolean).join(".");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,4 +10,5 @@ export * from "./api/use-api";
|
|||||||
export * from "./api/use-entity";
|
export * from "./api/use-entity";
|
||||||
export { useAuth } from "./schema/auth/use-auth";
|
export { useAuth } from "./schema/auth/use-auth";
|
||||||
export { Api, type TApiUser, type AuthState, type ApiOptions } from "../../Api";
|
export { Api, type TApiUser, type AuthState, type ApiOptions } from "../../Api";
|
||||||
|
export { FetchPromise } from "modules/ModuleApi";
|
||||||
export type { RepoQueryIn } from "data";
|
export type { RepoQueryIn } from "data";
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { DB } from "core";
|
||||||
import {
|
import {
|
||||||
type ComponentPropsWithRef,
|
type ComponentPropsWithRef,
|
||||||
type ComponentPropsWithoutRef,
|
type ComponentPropsWithoutRef,
|
||||||
@@ -23,6 +24,8 @@ export type FileState = {
|
|||||||
progress: number;
|
progress: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type FileStateWithData = FileState & { data: DB["media"] };
|
||||||
|
|
||||||
export type DropzoneRenderProps = {
|
export type DropzoneRenderProps = {
|
||||||
wrapperRef: RefObject<HTMLDivElement>;
|
wrapperRef: RefObject<HTMLDivElement>;
|
||||||
inputProps: ComponentPropsWithRef<"input">;
|
inputProps: ComponentPropsWithRef<"input">;
|
||||||
@@ -50,7 +53,7 @@ export type DropzoneProps = {
|
|||||||
autoUpload?: boolean;
|
autoUpload?: boolean;
|
||||||
onRejected?: (files: FileWithPath[]) => void;
|
onRejected?: (files: FileWithPath[]) => void;
|
||||||
onDeleted?: (file: FileState) => void;
|
onDeleted?: (file: FileState) => void;
|
||||||
onUploaded?: (files: FileState[]) => void;
|
onUploaded?: (files: FileStateWithData[]) => void;
|
||||||
placeholder?: {
|
placeholder?: {
|
||||||
show?: boolean;
|
show?: boolean;
|
||||||
text?: string;
|
text?: string;
|
||||||
@@ -172,15 +175,16 @@ export function Dropzone({
|
|||||||
setUploading(false);
|
setUploading(false);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
const uploaded: FileStateWithData[] = [];
|
||||||
for (const file of pendingFiles) {
|
for (const file of pendingFiles) {
|
||||||
try {
|
try {
|
||||||
await uploadFileProgress(file);
|
uploaded.push(await uploadFileProgress(file));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleUploadError(e);
|
handleUploadError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setUploading(false);
|
setUploading(false);
|
||||||
onUploaded?.(files);
|
onUploaded?.(uploaded);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
@@ -220,8 +224,8 @@ export function Dropzone({
|
|||||||
setFiles((prev) => prev.filter((f) => f.path !== path));
|
setFiles((prev) => prev.filter((f) => f.path !== path));
|
||||||
}
|
}
|
||||||
|
|
||||||
function uploadFileProgress(file: FileState) {
|
function uploadFileProgress(file: FileState): Promise<FileStateWithData> {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!file.body) {
|
if (!file.body) {
|
||||||
console.error("File has no body");
|
console.error("File has no body");
|
||||||
reject();
|
reject();
|
||||||
@@ -279,17 +283,19 @@ export function Dropzone({
|
|||||||
const response = JSON.parse(xhr.responseText);
|
const response = JSON.parse(xhr.responseText);
|
||||||
|
|
||||||
console.log("Response:", file, response);
|
console.log("Response:", file, response);
|
||||||
console.log("New state", response.state);
|
const newState = {
|
||||||
replaceFileState(file.path, {
|
|
||||||
...response.state,
|
...response.state,
|
||||||
progress: 1,
|
progress: 1,
|
||||||
state: "uploaded"
|
state: "uploaded"
|
||||||
});
|
};
|
||||||
|
|
||||||
|
replaceFileState(file.path, newState);
|
||||||
|
resolve({ ...response, ...file, ...newState });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setFileState(file.path, "uploaded", 1);
|
setFileState(file.path, "uploaded", 1);
|
||||||
console.error("Error parsing response", e);
|
console.error("Error parsing response", e);
|
||||||
|
reject(e);
|
||||||
}
|
}
|
||||||
resolve();
|
|
||||||
} else {
|
} else {
|
||||||
setFileState(file.path, "failed", 1);
|
setFileState(file.path, "failed", 1);
|
||||||
console.error("Upload failed with status: ", xhr.status, xhr.statusText);
|
console.error("Upload failed with status: ", xhr.status, xhr.statusText);
|
||||||
@@ -327,8 +333,8 @@ export function Dropzone({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function uploadFile(file: FileState) {
|
async function uploadFile(file: FileState) {
|
||||||
await uploadFileProgress(file);
|
const result = await uploadFileProgress(file);
|
||||||
onUploaded?.([file]);
|
onUploaded?.([result]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const openFileInput = () => inputRef.current?.click();
|
const openFileInput = () => inputRef.current?.click();
|
||||||
@@ -496,9 +502,9 @@ const Preview: React.FC<PreviewProps> = ({ file, handleUpload, handleDelete }) =
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col px-1.5 py-1">
|
<div className="flex flex-col px-1.5 py-1">
|
||||||
<p className="truncate">{file.name}</p>
|
<p className="truncate select-text">{file.name}</p>
|
||||||
<div className="flex flex-row justify-between text-sm font-mono opacity-50 text-nowrap gap-2">
|
<div className="flex flex-row justify-between text-sm font-mono opacity-50 text-nowrap gap-2">
|
||||||
<span className="truncate">{file.type}</span>
|
<span className="truncate select-text">{file.type}</span>
|
||||||
<span>{(file.size / 1024).toFixed(1)} KB</span>
|
<span>{(file.size / 1024).toFixed(1)} KB</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { mediaItemsToFileStates } from "./helper";
|
|||||||
|
|
||||||
export type DropzoneContainerProps = {
|
export type DropzoneContainerProps = {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
initialItems?: MediaFieldSchema[];
|
initialItems?: MediaFieldSchema[] | false;
|
||||||
entity?: {
|
entity?: {
|
||||||
name: string;
|
name: string;
|
||||||
id: number;
|
id: number;
|
||||||
@@ -18,6 +18,7 @@ export type DropzoneContainerProps = {
|
|||||||
};
|
};
|
||||||
media?: Pick<TAppMediaConfig, "entity_name" | "storage">;
|
media?: Pick<TAppMediaConfig, "entity_name" | "storage">;
|
||||||
query?: RepoQueryIn;
|
query?: RepoQueryIn;
|
||||||
|
randomFilename?: boolean;
|
||||||
} & Omit<Partial<DropzoneProps>, "children" | "initialItems">;
|
} & Omit<Partial<DropzoneProps>, "children" | "initialItems">;
|
||||||
|
|
||||||
const DropzoneContainerContext = createContext<DropzoneRenderProps>(undefined!);
|
const DropzoneContainerContext = createContext<DropzoneRenderProps>(undefined!);
|
||||||
@@ -28,6 +29,7 @@ export function DropzoneContainer({
|
|||||||
entity,
|
entity,
|
||||||
query,
|
query,
|
||||||
children,
|
children,
|
||||||
|
randomFilename,
|
||||||
...props
|
...props
|
||||||
}: DropzoneContainerProps) {
|
}: DropzoneContainerProps) {
|
||||||
const id = useId();
|
const id = useId();
|
||||||
@@ -57,12 +59,12 @@ export function DropzoneContainer({
|
|||||||
...query
|
...query
|
||||||
});
|
});
|
||||||
|
|
||||||
const $q = useApiQuery(selectApi, { enabled: !initialItems });
|
const $q = useApiQuery(selectApi, { enabled: initialItems !== false && !initialItems });
|
||||||
|
|
||||||
const getUploadInfo = useEvent((file) => {
|
const getUploadInfo = useEvent((file) => {
|
||||||
const url = entity
|
const url = entity
|
||||||
? api.media.getEntityUploadUrl(entity.name, entity.id, entity.field)
|
? api.media.getEntityUploadUrl(entity.name, entity.id, entity.field)
|
||||||
: api.media.getFileUploadUrl(file);
|
: api.media.getFileUploadUrl(randomFilename ? undefined : file);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url,
|
url,
|
||||||
@@ -79,7 +81,7 @@ export function DropzoneContainer({
|
|||||||
return api.media.deleteFile(file.path);
|
return api.media.deleteFile(file.path);
|
||||||
});
|
});
|
||||||
|
|
||||||
const actualItems = initialItems ?? (($q.data || []) as MediaFieldSchema[]);
|
const actualItems = (initialItems || $q.data || []) as MediaFieldSchema[];
|
||||||
const _initialItems = mediaItemsToFileStates(actualItems, { baseUrl });
|
const _initialItems = mediaItemsToFileStates(actualItems, { baseUrl });
|
||||||
|
|
||||||
const key = id + JSON.stringify(_initialItems);
|
const key = id + JSON.stringify(_initialItems);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export { useDropzone as useMediaDropzone };
|
|||||||
export type {
|
export type {
|
||||||
PreviewComponentProps,
|
PreviewComponentProps,
|
||||||
FileState,
|
FileState,
|
||||||
|
FileStateWithData,
|
||||||
DropzoneProps,
|
DropzoneProps,
|
||||||
DropzoneRenderProps
|
DropzoneRenderProps
|
||||||
} from "./Dropzone";
|
} from "./Dropzone";
|
||||||
|
|||||||
4
app/tsconfig.build.json
Normal file
4
app/tsconfig.build.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"include": ["./src/**/*.ts", "./src/**/*.tsx"]
|
||||||
|
}
|
||||||
@@ -33,6 +33,6 @@
|
|||||||
"bknd": ["./src/*"]
|
"bknd": ["./src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["./src/**/*.ts", "./src/**/*.tsx"],
|
"include": ["./src/**/*.ts", "./src/**/*.tsx", "vite.dev.ts", "build.ts"],
|
||||||
"exclude": ["node_modules", "dist", "dist/types", "**/*.d.ts"]
|
"exclude": ["node_modules", "dist", "dist/types", "**/*.d.ts"]
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ if (example) {
|
|||||||
|
|
||||||
let app: App;
|
let app: App;
|
||||||
const recreate = import.meta.env.VITE_APP_DISABLE_FRESH !== "1";
|
const recreate = import.meta.env.VITE_APP_DISABLE_FRESH !== "1";
|
||||||
let routesShown = false;
|
let firstStart = true;
|
||||||
export default {
|
export default {
|
||||||
async fetch(request: Request) {
|
async fetch(request: Request) {
|
||||||
if (!app || recreate) {
|
if (!app || recreate) {
|
||||||
@@ -48,8 +48,9 @@ export default {
|
|||||||
await app.build();
|
await app.build();
|
||||||
|
|
||||||
// log routes
|
// log routes
|
||||||
if (!routesShown) {
|
if (firstStart) {
|
||||||
routesShown = true;
|
console.log("[DB]", credentials);
|
||||||
|
firstStart = false;
|
||||||
console.log("\n\n[APP ROUTES]");
|
console.log("\n\n[APP ROUTES]");
|
||||||
showRoutes(app.server);
|
showRoutes(app.server);
|
||||||
console.log("-------\n\n");
|
console.log("-------\n\n");
|
||||||
|
|||||||
Reference in New Issue
Block a user