nuxt-file-storage 0.2.9 → 0.3.1-beta.1

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/README.md CHANGED
@@ -2,13 +2,14 @@
2
2
 
3
3
  # Nuxt File Storage
4
4
 
5
- [![Visits Badge](https://badges.pufler.dev/visits/nyllre/nuxt-file-storage)](https://badges.pufler.dev)
5
+ <!-- [![Visits Badge](https://badges.pufler.dev/visits/nyllre/nuxt-file-storage)](https://badges.pufler.dev) -->
6
6
  [![npm version][npm-version-src]][npm-version-href]
7
7
  [![npm downloads][npm-downloads-src]][npm-downloads-href]
8
+ [![nuxt.care health](https://img.shields.io/endpoint?url=https://nuxt.care/api/badge/nuxt-file-storage&style=flat&colorA=18181B&colorB=28CF8D)](https://nuxt.care/?search=nuxt-file-storage)
8
9
  [![License][license-src]][license-href]
9
10
  [![Nuxt][nuxt-src]][nuxt-href]
10
11
 
11
- Easy solution to store files in your nuxt apps. Be able to upload files from the frontend and recieve them from the backend to then save the files in your project.
12
+ Easy solution to store files in your nuxt apps. Be able to upload files from the frontend and receive them from the backend to then save the files in your project.
12
13
 
13
14
  - [✨ &nbsp;Release Notes](/CHANGELOG.md)
14
15
  - [🏀 Online playground](https://stackblitz.com/github/NyllRE/nuxt-file-storage?file=playground%2Fapp.vue)
@@ -128,13 +129,15 @@ You have to create a new instance of `useFileStorage` for each input field
128
129
  } = useFileStorage() ← | 2 |
129
130
  </script>
130
131
  ```
131
- by calling a new `useFileStorage` instance you seperate the internal logic between the inputs
132
+ by calling a new `useFileStorage` instance you separate the internal logic between the inputs
132
133
 
133
134
  ### Handling files in the backend
134
- using Nitro Server Engine, we will make an api route that recieves the files and stores them in the folder `userFiles`
135
+ using Nitro Server Engine, we will make an api route that receives the files and stores them in the folder `userFiles`
135
136
  ```ts
137
+ import { ServerFile } from "nuxt-file-storage";
138
+
136
139
  export default defineEventHandler(async (event) => {
137
- const { files } = await readBody<{ files: File[] }>(event)
140
+ const { files } = await readBody<{ files: ServerFile[] }>(event)
138
141
 
139
142
  for ( const file of files ) {
140
143
  await storeFileLocally(
@@ -149,13 +152,20 @@ export default defineEventHandler(async (event) => {
149
152
  const { binaryString, ext } = parseDataUrl(file.content)
150
153
  }
151
154
 
152
- return 'success!'
153
- })
155
+ // Deleting Files
156
+ await deleteFile('requiredFile.txt', '/userFiles')
154
157
 
155
- interface File {
156
- name: string
157
- content: string
158
- }
158
+ // Get file path
159
+ return await getFileLocally('requiredFile.txt', '/userFiles')
160
+ // returns: {AbsolutePath}/userFiles/requiredFile.txt
161
+
162
+ // Return a NodeStream of the file
163
+ // uses getFileLocally internally
164
+ return await retrieveFileLocally(event, 'requiredFile.txt', '/userFiles')
165
+
166
+ // Get all files in a folder
167
+ return await getFilesLocally('/userFiles')
168
+ })
159
169
  ```
160
170
 
161
171
  And that's it! Now you can store any file in your nuxt project from the user ✨
package/dist/module.d.mts CHANGED
@@ -1,24 +1,7 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
+ import { ModuleOptions } from '../dist/runtime/types.js';
3
+ export * from '../dist/runtime/types.js';
2
4
 
3
- interface ServerFile {
4
- name: string
5
- content: string
6
- size: string
7
- type: string
8
- lastModified: string
9
- }
5
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
10
6
 
11
- interface ClientFile extends Blob {
12
- content: string | ArrayBuffer | null | undefined
13
- name: string
14
- lastModified: number
15
- }
16
-
17
- interface ModuleOptions {
18
- mount: string
19
- version: string
20
- }
21
-
22
- declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
23
-
24
- export { type ClientFile, type ModuleOptions, type ServerFile, _default as default };
7
+ export { _default as default };
package/dist/module.json CHANGED
@@ -1,5 +1,9 @@
1
1
  {
2
2
  "name": "nuxt-file-storage",
3
3
  "configKey": "fileStorage",
4
- "version": "0.2.9"
4
+ "version": "0.3.1-beta.1",
5
+ "builder": {
6
+ "@nuxt/module-builder": "1.0.2",
7
+ "unbuild": "3.6.1"
8
+ }
5
9
  }
package/dist/module.mjs CHANGED
@@ -1,9 +1,7 @@
1
- import { defineNuxtModule, logger, createResolver, addImportsDir, addServerScanDir } from '@nuxt/kit';
2
- import defu from 'defu';
1
+ import { defineNuxtModule, createResolver, logger, addImportsDir, addServerScanDir, addTemplate } from '@nuxt/kit';
2
+ import { defu } from 'defu';
3
3
 
4
- const version = "0.2.9";
5
-
6
- const module = defineNuxtModule({
4
+ const module$1 = defineNuxtModule({
7
5
  meta: {
8
6
  name: "nuxt-file-storage",
9
7
  configKey: "fileStorage"
@@ -14,26 +12,35 @@ const module = defineNuxtModule({
14
12
  // version: '0.0.0',
15
13
  // },
16
14
  setup(options, nuxt) {
15
+ const { resolve } = createResolver(import.meta.url);
17
16
  const config = nuxt.options.runtimeConfig;
18
17
  config.public.fileStorage = defu(config.public.fileStorage, {
19
18
  ...options
20
19
  });
21
- if (nuxt.options.dev) {
22
- //! I couldn't find a way to detect new updates to warn one time so it will warn every time the server runs again, if you know how to warn about a breaking change only the first run after an update feel free to open a new pull request
23
- const previousVersion = config.public.fileStorage?.version;
24
- if (!previousVersion || previousVersion !== version) {
25
- logger.warn(
26
- `There is a breaking change in the \`storeFileLocally\` method, link to changelog:
27
- https://github.com/NyllRE/nuxt-file-storage/releases/tag/v${version}
28
- `
29
- );
30
- config.public.fileStorage.version = version;
31
- }
32
- }
33
- const resolve = createResolver(import.meta.url).resolve;
20
+ logger.ready(`Nuxt File Storage has mounted successfully`);
21
+ nuxt.options.alias["#file-storage"] = resolve("./runtime");
34
22
  addImportsDir(resolve("runtime/composables"));
35
23
  addServerScanDir(resolve("./runtime/server"));
24
+ addTemplate({
25
+ filename: "types/nuxt-file-storage.d.ts",
26
+ getContents: () => [
27
+ "declare module '#file-storage' {",
28
+ ` const ServerFile: typeof import('${resolve("./runtime/types")}').ServerFile`,
29
+ ` const ClientFile: typeof import('${resolve("./runtime/types")}').ClientFile`,
30
+ "}",
31
+ "",
32
+ "declare global {",
33
+ ` type ServerFile = import('${resolve("./runtime/types")}').ServerFile`,
34
+ ` type ClientFile = import('${resolve("./runtime/types")}').ClientFile`,
35
+ "}",
36
+ "",
37
+ "export {}"
38
+ ].join("\n")
39
+ });
40
+ nuxt.hook("prepare:types", async (options2) => {
41
+ options2.references.push({ path: resolve(nuxt.options.buildDir, "types/nuxt-file-storage.d.ts") });
42
+ });
36
43
  }
37
44
  });
38
45
 
39
- export { module as default };
46
+ export { module$1 as default };
@@ -2,21 +2,8 @@ type Options = {
2
2
  clearOldFiles: boolean;
3
3
  };
4
4
  export default function (options?: Options): {
5
- files: import("vue").Ref<{
6
- content: string | {
7
- readonly byteLength: number;
8
- slice: (begin: number, end?: number | undefined) => ArrayBuffer;
9
- readonly [Symbol.toStringTag]: string;
10
- } | null | undefined;
11
- name: string;
12
- lastModified: number;
13
- readonly size: number;
14
- readonly type: string;
15
- arrayBuffer: () => Promise<ArrayBuffer>;
16
- slice: (start?: number | undefined, end?: number | undefined, contentType?: string | undefined) => Blob;
17
- stream: () => ReadableStream<Uint8Array>;
18
- text: () => Promise<string>;
19
- }[]>;
5
+ files: import("vue").Ref<any, any>;
20
6
  handleFileInput: (event: any) => Promise<void>;
7
+ clearFiles: () => void;
21
8
  };
22
9
  export {};
@@ -21,9 +21,12 @@ export default function(options = { clearOldFiles: true }) {
21
21
  reader.readAsDataURL(file);
22
22
  });
23
23
  };
24
+ const clearFiles = () => {
25
+ files.value.splice(0, files.value.length);
26
+ };
24
27
  const handleFileInput = async (event) => {
25
28
  if (options.clearOldFiles) {
26
- files.value.splice(0, files.value.length);
29
+ clearFiles();
27
30
  }
28
31
  const promises = [];
29
32
  for (const file of event.target.files) {
@@ -33,6 +36,7 @@ export default function(options = { clearOldFiles: true }) {
33
36
  };
34
37
  return {
35
38
  files,
36
- handleFileInput
39
+ handleFileInput,
40
+ clearFiles
37
41
  };
38
42
  }
@@ -0,0 +1,9 @@
1
+ export declare const normalizeRelative: (p: string) => string;
2
+ export declare const isSafeBasename: (name: string) => boolean;
3
+ export declare const ensureSafeBasename: (name: string) => string;
4
+ export declare const containsPathTraversal: (p: string) => boolean;
5
+ /**
6
+ * Resolve a target path relative to a mount and ensure it cannot escape the mount.
7
+ * Throws on any suspicious input (path traversal, symlink escape, absolute outside mount).
8
+ */
9
+ export declare const resolveAndEnsureInside: (mount: string, ...parts: string[]) => Promise<string>;
@@ -0,0 +1,57 @@
1
+ import path from "path";
2
+ import { realpath, stat } from "fs/promises";
3
+ import { createError } from "#imports";
4
+ export const normalizeRelative = (p) => {
5
+ if (!p) return "";
6
+ return p.replace(/^[/\\]+/, "").replace(/\\/g, "/");
7
+ };
8
+ export const isSafeBasename = (name) => {
9
+ if (!name) return false;
10
+ if (name !== path.basename(name)) return false;
11
+ if (name.includes("\0")) return false;
12
+ if (name === "." || name === "..") return false;
13
+ if (name.includes("/") || name.includes("\\")) return false;
14
+ if (name.split(/[/\\]+/).includes("..")) return false;
15
+ return true;
16
+ };
17
+ export const ensureSafeBasename = (name) => {
18
+ if (!isSafeBasename(name)) throw new Error("Unsafe filename");
19
+ return name;
20
+ };
21
+ export const containsPathTraversal = (p) => {
22
+ if (!p) return false;
23
+ if (/^([\\/]|^)?\.\.([\\/]|$)?/.test(p) || /(^|[\\/])\.\.($|[\\/])/.test(p)) return true;
24
+ const normalized = path.normalize(p);
25
+ const parts = normalized.split(/[/\\]+/);
26
+ return parts.includes("..");
27
+ };
28
+ export const resolveAndEnsureInside = async (mount, ...parts) => {
29
+ if (!mount) throw new Error("Mount path must be provided");
30
+ const mountResolved = path.resolve(mount);
31
+ const cleanedParts = parts.map((p) => p.replace(/^[/\\]+/, ""));
32
+ const targetResolved = path.resolve(mountResolved, ...cleanedParts);
33
+ const relative = path.relative(mountResolved, targetResolved);
34
+ if (relative === "" || !relative.startsWith(".." + path.sep) && relative !== "..") {
35
+ let cur = targetResolved;
36
+ while (cur) {
37
+ try {
38
+ await stat(cur);
39
+ break;
40
+ } catch (error) {
41
+ const parent = path.dirname(cur);
42
+ if (parent === cur) break;
43
+ cur = parent;
44
+ }
45
+ }
46
+ const mountReal = await realpath(mountResolved);
47
+ const curReal = await realpath(cur);
48
+ if (!curReal.startsWith(mountReal)) {
49
+ throw new Error("Resolved path escapes configured mount (symlink detected)");
50
+ }
51
+ return targetResolved;
52
+ }
53
+ throw createError({
54
+ statusCode: 400,
55
+ statusMessage: "Resolved path is outside of configured mount"
56
+ });
57
+ };
@@ -1,25 +1,68 @@
1
- /// <reference types="node" />
2
- import type { ServerFile } from '../../../types';
1
+ import type { ServerFile } from '../../types.js';
2
+ import type { H3Event, EventHandlerRequest } from 'h3';
3
3
  /**
4
- * #### Will store the file in the specified directory
4
+ * @description Will store the file in the specified directory
5
+ * @param file provide the file object
6
+ * @param fileNameOrIdLength you can pass a string or a number, if you enter a string it will be the file name, if you enter a number it will generate a unique ID
7
+ * @param filelocation provide the folder you wish to locate the file in
5
8
  * @returns file name: `${filename}`.`${fileExtension}`
6
- * @prop `file`: provide the file object
7
- * @prop `fileNameOrIdLength`: you can pass a string or a number, if you enter a string it will be the file name, if you enter a number it will generate a unique ID
8
- * @prop `filelocation`: provide the folder you wish to locate the file in
9
+ *
10
+ *
11
+ * [Documentation](https://github.com/NyllRE/nuxt-file-storage#handling-files-in-the-backend)
12
+ *
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import type { ServerFile } from "nuxt-file-storage";
17
+ *
18
+ * export default defineEventHandler(async (event) => {
19
+ * const { file } = await readBody<{ file: ServerFile }>(event);
20
+ * await storeFileLocally( file, 8, '/userFiles' );
21
+ * })
22
+ * ```
9
23
  */
10
24
  export declare const storeFileLocally: (file: ServerFile, fileNameOrIdLength: string | number, filelocation?: string) => Promise<string>;
11
25
  /**
12
- *
13
- * @param `filename`: the name of the file you want to delete
14
- * @param `filelocation`: the folder where the file is located, if it is in the root folder you can leave it empty, if it is in a subfolder you can pass the name of the subfolder with a preceding slash: `/subfolder`
26
+ * @description Get file path in the specified directory
27
+ * @param filename provide the file name (return of storeFileLocally)
28
+ * @param filelocation provide the folder you wish to locate the file in
29
+ * @returns file path: `${config.fileStorage.mount}/${filelocation}/${filename}`
30
+ */
31
+ export declare const getFileLocally: (filename: string, filelocation?: string) => string;
32
+ /**
33
+ * @description Get all files in the specified directory
34
+ * @param filelocation provide the folder you wish to locate the file in
35
+ * @returns all files in filelocation: `${config.fileStorage.mount}/${filelocation}`
36
+ */
37
+ export declare const getFilesLocally: (filelocation?: string) => Promise<string[]>;
38
+ /**
39
+ * @param filename the name of the file you want to delete
40
+ * @param filelocation the folder where the file is located, if it is in the root folder you can leave it empty, if it is in a subfolder you can pass the name of the subfolder with a preceding slash: `/subfolder`
41
+ * @example
42
+ * ```ts
43
+ * await deleteFile('/userFiles', 'requiredFile.txt')
44
+ * ```
15
45
  */
16
46
  export declare const deleteFile: (filename: string, filelocation?: string) => Promise<void>;
17
47
  /**
18
- Parses a data URL and returns an object with the binary data and the file extension.
19
- @param {string} file - The data URL
20
- @returns {{ binaryString: Buffer, ext: string }} - An object with the binary data and the file extension
48
+ * @description Parses a data URL and returns an object with the binary data and the file extension.
49
+ * @param {string} file - The data URL
50
+ * @returns {{binaryString: Buffer, ext: string}} An object with the binary data - file extension
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * const { binaryString, ext } = parseDataUrl(file.content)
55
+ * ```
21
56
  */
22
57
  export declare const parseDataUrl: (file: string) => {
23
58
  binaryString: Buffer;
24
59
  ext: string;
25
60
  };
61
+ /**
62
+ * Retrieve a file as a readable stream from local storage
63
+ * @param event H3 event to set response headers
64
+ * @param filename name of the file to retrieve
65
+ * @param filelocation folder where the file is located
66
+ * @returns Readable stream of the file
67
+ */
68
+ export declare const retrieveFileLocally: (event: H3Event<EventHandlerRequest>, filename: string, filelocation?: string) => Promise<NodeJS.ReadableStream>;
@@ -0,0 +1,142 @@
1
+ import { writeFile, rm, mkdir, readdir } from "fs/promises";
2
+ import path from "path";
3
+ import {
4
+ normalizeRelative,
5
+ ensureSafeBasename,
6
+ resolveAndEnsureInside
7
+ } from "./path-safety.js";
8
+ import { createError, useRuntimeConfig } from "#imports";
9
+ import { createReadStream, promises as fsPromises } from "fs";
10
+ const getMount = () => {
11
+ try {
12
+ return useRuntimeConfig().public.fileStorage.mount;
13
+ } catch (err) {
14
+ return process.env.FILE_STORAGE_MOUNT || process.env.NUXT_FILE_STORAGE_MOUNT;
15
+ }
16
+ };
17
+ export const storeFileLocally = async (file, fileNameOrIdLength, filelocation = "") => {
18
+ const { binaryString, ext } = parseDataUrl(file.content);
19
+ const location = getMount();
20
+ if (!location) throw new Error("fileStorage.mount is not configured");
21
+ const nameStr = file.name.toString();
22
+ const originalExt = nameStr.includes(".") ? nameStr.split(".").pop() : ext;
23
+ const safeExt = (originalExt || ext).replace(/[^a-zA-Z0-9]/g, "") || ext;
24
+ let filename;
25
+ if (typeof fileNameOrIdLength === "number") {
26
+ filename = `${generateRandomId(fileNameOrIdLength)}.${safeExt}`;
27
+ } else {
28
+ ensureSafeBasename(fileNameOrIdLength);
29
+ const extensionFromFileName = fileNameOrIdLength.split(".").pop();
30
+ if (!fileNameOrIdLength.includes(".")) {
31
+ filename = `${fileNameOrIdLength}.${safeExt}`;
32
+ } else if (extensionFromFileName === safeExt) {
33
+ filename = fileNameOrIdLength;
34
+ } else {
35
+ console.warn(
36
+ `[nuxt-file-storage] The provided filename "${fileNameOrIdLength}" does not have the expected extension ".${safeExt}". The correct extension will be appended.`
37
+ );
38
+ filename = `${fileNameOrIdLength.split(".").slice(0, -1).join(".")}.${safeExt}`;
39
+ }
40
+ }
41
+ const normalizedFilelocation = normalizeRelative(filelocation);
42
+ const dirPath = await resolveAndEnsureInside(location, normalizedFilelocation);
43
+ try {
44
+ await mkdir(dirPath, { recursive: true });
45
+ } catch (err) {
46
+ if (err?.code === "EEXIST") {
47
+ throw new Error(
48
+ `[nuxt-file-storage] EEXIST: A file already exists at "${dirPath}" where a directory was expected. This typically happens when a file was accidentally created at a path meant for a folder. Please remove or rename the conflicting file.`
49
+ );
50
+ } else if (err?.code === "ENOTDIR") {
51
+ throw new Error(
52
+ `[nuxt-file-storage] ENOTDIR: Cannot create directory "${dirPath}" because a parent path component is a file, not a directory. Check if any part of the path "${normalizedFilelocation}" exists as a file instead of a folder. Please remove or rename the conflicting file.`
53
+ );
54
+ }
55
+ throw err;
56
+ }
57
+ const targetPath = await resolveAndEnsureInside(location, normalizedFilelocation, filename);
58
+ await writeFile(targetPath, binaryString, {
59
+ flag: "w"
60
+ });
61
+ return filename;
62
+ };
63
+ export const getFileLocally = (filename, filelocation = "") => {
64
+ const location = getMount();
65
+ if (!location) throw new Error("fileStorage.mount is not configured");
66
+ ensureSafeBasename(filename);
67
+ const normalizedFilelocation = normalizeRelative(filelocation);
68
+ const resolved = path.resolve(location, normalizedFilelocation, filename);
69
+ const mountResolved = path.resolve(location);
70
+ const relative = path.relative(mountResolved, resolved);
71
+ if (relative === "" || !relative.startsWith(".." + path.sep) && relative !== "..") {
72
+ return resolved;
73
+ }
74
+ throw createError({
75
+ statusCode: 400,
76
+ statusMessage: "Resolved path is outside of configured mount"
77
+ });
78
+ };
79
+ export const getFilesLocally = async (filelocation = "") => {
80
+ const location = getMount();
81
+ if (!location) return [];
82
+ const normalizedFilelocation = normalizeRelative(filelocation);
83
+ const dirPath = await resolveAndEnsureInside(location, normalizedFilelocation);
84
+ return await readdir(dirPath).catch(() => []);
85
+ };
86
+ export const deleteFile = async (filename, filelocation = "") => {
87
+ const location = getMount();
88
+ if (!location) throw new Error("fileStorage.mount is not configured");
89
+ ensureSafeBasename(filename);
90
+ const normalizedFilelocation = normalizeRelative(filelocation);
91
+ const targetPath = await resolveAndEnsureInside(location, normalizedFilelocation, filename);
92
+ await rm(targetPath);
93
+ };
94
+ const generateRandomId = (length) => {
95
+ const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
96
+ let randomId = "";
97
+ for (let i = 0; i < length; i++) {
98
+ randomId += characters.charAt(Math.floor(Math.random() * characters.length));
99
+ }
100
+ return randomId;
101
+ };
102
+ export const parseDataUrl = (file) => {
103
+ const arr = file.split(",");
104
+ const mimeMatch = arr[0].match(/:(.*?);/);
105
+ if (!mimeMatch) {
106
+ throw new Error("Invalid data URL");
107
+ }
108
+ const mime = mimeMatch[1];
109
+ const base64String = arr[1];
110
+ const binaryString = Buffer.from(base64String, "base64");
111
+ const ext = mime.split("/")[1];
112
+ return { binaryString, ext };
113
+ };
114
+ export const retrieveFileLocally = async (event, filename, filelocation = "") => {
115
+ const filePath = getFileLocally(filename, filelocation);
116
+ let stats;
117
+ try {
118
+ stats = await fsPromises.stat(filePath);
119
+ if (!stats.isFile()) {
120
+ throw createError({ statusCode: 404, statusMessage: "Not Found" });
121
+ }
122
+ } catch (err) {
123
+ throw createError({ statusCode: 404, statusMessage: "Not Found" });
124
+ }
125
+ const ext = path.extname(filePath).slice(1).toLowerCase();
126
+ const mimeMap = {
127
+ png: "image/png",
128
+ jpg: "image/jpeg",
129
+ jpeg: "image/jpeg",
130
+ gif: "image/gif",
131
+ svg: "image/svg+xml",
132
+ pdf: "application/pdf",
133
+ txt: "text/plain",
134
+ html: "text/html",
135
+ json: "application/json"
136
+ };
137
+ const contentType = mimeMap[ext] || "application/octet-stream";
138
+ event.node.res.setHeader("Content-Type", contentType);
139
+ event.node.res.setHeader("Content-Length", String(stats.size));
140
+ event.node.res.setHeader("Content-Disposition", `inline; filename="${path.basename(filePath)}"`);
141
+ return createReadStream(filePath);
142
+ };
@@ -0,0 +1,23 @@
1
+ export interface ServerFile {
2
+ name: string;
3
+ content: string;
4
+ size: string;
5
+ type: string;
6
+ lastModified: string;
7
+ }
8
+ export interface ClientFile extends Blob {
9
+ content: string | ArrayBuffer | null | undefined;
10
+ name: string;
11
+ lastModified: number;
12
+ }
13
+ export interface ModuleOptions {
14
+ mount: string;
15
+ version: string;
16
+ }
17
+ /**
18
+ * @description Augment the '#imports' module to include useRuntimeConfig
19
+ * this is only needed because this package is consumed as a module
20
+ */
21
+ declare module '#imports' {
22
+ function useRuntimeConfig(): any;
23
+ }
File without changes
package/dist/types.d.mts CHANGED
@@ -1,16 +1,9 @@
1
+ import type { NuxtModule } from '@nuxt/schema'
1
2
 
2
- import type { ModuleOptions } from './module.js'
3
+ import type { default as Module } from './module.mjs'
3
4
 
5
+ export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
4
6
 
5
- declare module '@nuxt/schema' {
6
- interface NuxtConfig { ['fileStorage']?: Partial<ModuleOptions> }
7
- interface NuxtOptions { ['fileStorage']?: ModuleOptions }
8
- }
7
+ export { default } from './module.mjs'
9
8
 
10
- declare module 'nuxt/schema' {
11
- interface NuxtConfig { ['fileStorage']?: Partial<ModuleOptions> }
12
- interface NuxtOptions { ['fileStorage']?: ModuleOptions }
13
- }
14
-
15
-
16
- export type { ClientFile, ModuleOptions, ServerFile, default } from './module.js'
9
+ export * from '../dist/runtime/types.js'
package/package.json CHANGED
@@ -1,46 +1,50 @@
1
1
  {
2
- "name": "nuxt-file-storage",
3
- "version": "0.2.9",
4
- "description": "Easy solution to store files in your nuxt apps. Be able to upload files from the frontend and recieve them from the backend to then save the files in your project.",
5
- "repository": "NyllRE/nuxt-file-storage",
6
- "license": "MIT",
7
- "type": "module",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/types.d.ts",
11
- "import": "./dist/module.mjs",
12
- "require": "./dist/module.cjs"
13
- }
14
- },
15
- "main": "./dist/module.cjs",
16
- "types": "./dist/types.d.ts",
17
- "files": [
18
- "dist"
19
- ],
20
- "scripts": {
21
- "prepack": "nuxt-module-build build",
22
- "dev": "nuxi dev playground",
23
- "dev:build": "nuxi build playground",
24
- "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
25
- "release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
26
- "lint": "eslint .",
27
- "test": "vitest run",
28
- "test:watch": "vitest watch"
29
- },
30
- "dependencies": {
31
- "@nuxt/kit": "^3.9.3",
32
- "defu": "^6.1.4"
33
- },
34
- "devDependencies": {
35
- "@nuxt/devtools": "latest",
36
- "@nuxt/eslint-config": "^0.2.0",
37
- "@nuxt/module-builder": "^0.8.3",
38
- "@nuxt/schema": "^3.9.3",
39
- "@nuxt/test-utils": "^3.9.0",
40
- "@types/node": "^20.11.5",
41
- "changelogen": "^0.5.5",
42
- "eslint": "^8.56.0",
43
- "nuxt": "^3.9.3",
44
- "vitest": "^1.0.0"
45
- }
46
- }
2
+ "name": "nuxt-file-storage",
3
+ "version": "0.3.1-beta.1",
4
+ "description": "Easy solution to store files in your nuxt apps. Be able to upload files from the frontend and recieve them from the backend to then save the files in your project.",
5
+ "repository": "NyllRE/nuxt-file-storage",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/types.d.mts",
11
+ "import": "./dist/module.mjs"
12
+ }
13
+ },
14
+ "main": "./dist/module.mjs",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "prepack": "nuxt-module-build build",
20
+ "dev": "nuxi dev playground",
21
+ "dev:build": "nuxi build playground",
22
+ "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
23
+ "bump": "npm version patch -m \"chore(release): %s\"",
24
+ "release": "npm run lint && npm run bump && npm run prepack && changelogen && git push --follow-tags && npm publish",
25
+ "lint": "eslint .",
26
+ "test": "vitest run",
27
+ "test:watch": "vitest watch"
28
+ },
29
+ "dependencies": {
30
+ "@nuxt/kit": "^4.3.0",
31
+ "defu": "^6.1.4"
32
+ },
33
+ "devDependencies": {
34
+ "@nuxt/devtools": "^3.1.1",
35
+ "@nuxt/eslint-config": "^1.13.0",
36
+ "@nuxt/module-builder": "^1.0.2",
37
+ "@nuxt/schema": "^4.3.0",
38
+ "@nuxt/test-utils": "^3.23.0",
39
+ "@types/node": "^20.17.19",
40
+ "@vitest/ui": "4.0.16",
41
+ "@vue/test-utils": "^2.4.6",
42
+ "changelogen": "^0.5.7",
43
+ "eslint": "^8.57.1",
44
+ "happy-dom": "^20.0.11",
45
+ "nuxt": "^4.3.0",
46
+ "playwright-core": "^1.57.0",
47
+ "typescript": "^5.9.3",
48
+ "vitest": "^4.0.16"
49
+ }
50
+ }
package/dist/module.cjs DELETED
@@ -1,5 +0,0 @@
1
- module.exports = function(...args) {
2
- return import('./module.mjs').then(m => m.default.call(this, ...args))
3
- }
4
- const _meta = module.exports.meta = require('./module.json')
5
- module.exports.getMeta = () => Promise.resolve(_meta)
package/dist/module.d.ts DELETED
@@ -1,24 +0,0 @@
1
- import * as _nuxt_schema from '@nuxt/schema';
2
-
3
- interface ServerFile {
4
- name: string
5
- content: string
6
- size: string
7
- type: string
8
- lastModified: string
9
- }
10
-
11
- interface ClientFile extends Blob {
12
- content: string | ArrayBuffer | null | undefined
13
- name: string
14
- lastModified: number
15
- }
16
-
17
- interface ModuleOptions {
18
- mount: string
19
- version: string
20
- }
21
-
22
- declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
23
-
24
- export { type ClientFile, type ModuleOptions, type ServerFile, _default as default };
@@ -1,37 +0,0 @@
1
- import { writeFile, rm, mkdir } from "fs/promises";
2
- import { useRuntimeConfig } from "#imports";
3
- export const storeFileLocally = async (file, fileNameOrIdLength, filelocation = "") => {
4
- const { binaryString, ext } = parseDataUrl(file.content);
5
- const location = useRuntimeConfig().public.fileStorage.mount;
6
- const originalExt = file.name.toString().split(".").pop() || ext;
7
- const filename = typeof fileNameOrIdLength == "number" ? `${generateRandomId(fileNameOrIdLength)}.${originalExt}` : `${fileNameOrIdLength}.${originalExt}`;
8
- await mkdir(`${location}${filelocation}`, { recursive: true });
9
- await writeFile(`${location}${filelocation}/${filename}`, binaryString, {
10
- flag: "w"
11
- });
12
- return filename;
13
- };
14
- export const deleteFile = async (filename, filelocation = "") => {
15
- const location = useRuntimeConfig().public.fileStorage.mount;
16
- await rm(`${location}${filelocation}/${filename}`);
17
- };
18
- const generateRandomId = (length) => {
19
- const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
20
- let randomId = "";
21
- for (let i = 0; i < length; i++) {
22
- randomId += characters.charAt(Math.floor(Math.random() * characters.length));
23
- }
24
- return randomId;
25
- };
26
- export const parseDataUrl = (file) => {
27
- const arr = file.split(",");
28
- const mimeMatch = arr[0].match(/:(.*?);/);
29
- if (!mimeMatch) {
30
- throw new Error("Invalid data URL");
31
- }
32
- const mime = mimeMatch[1];
33
- const base64String = arr[1];
34
- const binaryString = Buffer.from(base64String, "base64");
35
- const ext = mime.split("/")[1];
36
- return { binaryString, ext };
37
- };
package/dist/types.d.ts DELETED
@@ -1,16 +0,0 @@
1
-
2
- import type { ModuleOptions } from './module'
3
-
4
-
5
- declare module '@nuxt/schema' {
6
- interface NuxtConfig { ['fileStorage']?: Partial<ModuleOptions> }
7
- interface NuxtOptions { ['fileStorage']?: ModuleOptions }
8
- }
9
-
10
- declare module 'nuxt/schema' {
11
- interface NuxtConfig { ['fileStorage']?: Partial<ModuleOptions> }
12
- interface NuxtOptions { ['fileStorage']?: ModuleOptions }
13
- }
14
-
15
-
16
- export type { ClientFile, ModuleOptions, ServerFile, default } from './module'
File without changes