nuxt-filer 0.0.16 → 0.0.17
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 +112 -4
- package/dist/module.d.mts +19 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +40 -8
- package/dist/runtime/server/handlers/image-service.d.ts +12 -0
- package/dist/runtime/server/handlers/image-service.js +21 -0
- package/dist/runtime/server/handlers/ipx.js +32 -14
- package/dist/runtime/server/plugins/default-storage.d.ts +2 -1
- package/dist/runtime/server/plugins/default-storage.js +2 -0
- package/dist/runtime/server/providers/drizzle.d.ts +75 -0
- package/dist/runtime/server/providers/drizzle.js +225 -0
- package/dist/runtime/server/providers/s3.d.ts +5 -0
- package/dist/runtime/server/providers/s3.js +14 -2
- package/dist/runtime/server/utils/image-service-runtime.d.ts +9 -0
- package/dist/runtime/server/utils/image-service-runtime.js +65 -0
- package/dist/runtime/server/utils/image-service.d.ts +36 -0
- package/dist/runtime/server/utils/image-service.js +113 -0
- package/dist/runtime/server/utils/storage.js +3 -1
- package/package.json +24 -24
- package/src/runtime/server/handlers/image-service.ts +35 -0
- package/src/runtime/server/handlers/ipx.ts +55 -20
- package/src/runtime/server/plugins/default-storage.ts +6 -1
- package/src/runtime/server/providers/drizzle.ts +379 -0
- package/src/runtime/server/providers/s3.ts +21 -2
- package/src/runtime/server/utils/image-service-runtime.ts +87 -0
- package/src/runtime/server/utils/image-service.ts +166 -0
- package/src/runtime/server/utils/image.ts +2 -2
- package/src/runtime/server/utils/storage.ts +8 -3
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ File storage module for Nuxt. Provides a server-side `useFileStorage()` composab
|
|
|
9
9
|
|
|
10
10
|
## Features
|
|
11
11
|
|
|
12
|
-
- **Pluggable provider architecture** —
|
|
12
|
+
- **Pluggable provider architecture** — built-in unstorage (default), S3 and Drizzle providers, or bring your own
|
|
13
13
|
- **File versioning** — built-in version tracking, latest-version filtering, and duplicate detection
|
|
14
14
|
- **External file sync** — two-way sync with external systems (Jira, SharePoint, etc.) via optional provider interface
|
|
15
15
|
- **Zero-config default** — works out of the box with local filesystem storage, no database required
|
|
@@ -32,7 +32,8 @@ npx nuxi module add nuxt-filer
|
|
|
32
32
|
export default defineNuxtConfig({
|
|
33
33
|
modules: ['nuxt-filer'],
|
|
34
34
|
filer: {
|
|
35
|
-
// Nitro storage mount name (default: 'documents')
|
|
35
|
+
// Nitro storage mount name (default: 'documents'). Mounted on the local fs
|
|
36
|
+
// for every provider unless you configure it yourself under `nitro.storage`.
|
|
36
37
|
storageName: 'documents',
|
|
37
38
|
// Base path for fs-lite driver (default: '.data/documents')
|
|
38
39
|
storagePath: '.data/documents',
|
|
@@ -192,7 +193,45 @@ filer: {
|
|
|
192
193
|
},
|
|
193
194
|
```
|
|
194
195
|
|
|
195
|
-
`@nuxt/image` and `ipx` are declared as optional peer dependencies — they only need to be installed if you want to use this integration.
|
|
196
|
+
`@nuxt/image` and `ipx` are declared as optional peer dependencies — they only need to be installed if you want to use this integration. Both ipx 3 and ipx 4 (pulled in by `@nuxt/image` 2.1+) are supported.
|
|
197
|
+
|
|
198
|
+
### External image service (imgproxy / standalone IPX)
|
|
199
|
+
|
|
200
|
+
Image processing can run in a separate service instead of this server, so
|
|
201
|
+
`sharp` and `ipx` aren't needed here and one service can serve many apps:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
filer: {
|
|
205
|
+
image: {
|
|
206
|
+
service: 'imgproxy', // or 'ipx' for a standalone `npx ipx serve`; default 'local'
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
NUXT_FILER_IMAGE_BASE_URL=https://img.example.com
|
|
213
|
+
NUXT_FILER_IMAGE_KEY=... # imgproxy signing key + salt (hex); unsigned URLs when unset
|
|
214
|
+
NUXT_FILER_IMAGE_SALT=...
|
|
215
|
+
NUXT_FILER_IMAGE_SOURCE_URL=http://app:3000 # how the service reaches this app
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
- `<NuxtImg provider="filer">` works unchanged. The image route redirects to a
|
|
219
|
+
signed service URL, so the signing key never reaches the browser.
|
|
220
|
+
- The service fetches originals from `/_filer-ipx/_/<groupId>/<fileId>`, which
|
|
221
|
+
serves the stored bytes. `sourceURL` is the origin it uses for that — e.g. the
|
|
222
|
+
app's address on a private Docker network. Without it, the request's origin
|
|
223
|
+
is used.
|
|
224
|
+
- Upload-time transforms (`upload(..., { transform })`) go through the service
|
|
225
|
+
too: the original is staged under the `_filer-transform` group, the variant
|
|
226
|
+
is fetched, and the staged copy is removed. This requires `sourceURL`.
|
|
227
|
+
`transformImage()` itself still needs `sharp`.
|
|
228
|
+
- imgproxy receives the IPX modifiers translated to its options (`w`, `h`,
|
|
229
|
+
`s`, `fit`, `enlarge`, `q`, `f`, `b`, `pos`, `blur`, `sharpen`, `rotate`);
|
|
230
|
+
modifiers without an equivalent are dropped. A standalone IPX gets them as-is.
|
|
231
|
+
- imgproxy blocks loopback and private source addresses by default. If
|
|
232
|
+
`sourceURL` points at one, set `IMGPROXY_ALLOW_LOOPBACK_SOURCE_ADDRESSES` /
|
|
233
|
+
`IMGPROXY_ALLOW_PRIVATE_SOURCE_ADDRESSES`. A standalone IPX needs the source
|
|
234
|
+
host in `--domains`.
|
|
196
235
|
|
|
197
236
|
## Resumable uploads (tus)
|
|
198
237
|
|
|
@@ -323,9 +362,78 @@ export default defineNitroPlugin(() => {
|
|
|
323
362
|
|
|
324
363
|
> `createS3Provider` requires the optional [`aws4fetch`](https://github.com/mhart/aws4fetch) peer dependency (`npm i aws4fetch`). Pass a custom `client` to use a different transport or to unit-test without network.
|
|
325
364
|
|
|
365
|
+
## Drizzle (metadata in your database)
|
|
366
|
+
|
|
367
|
+
`createDrizzleProvider` keeps file metadata in a table of your own
|
|
368
|
+
[Drizzle](https://orm.drizzle.team) database and the bytes in a blob store —
|
|
369
|
+
S3/R2 via `createS3Client()`, or any mounted Nitro storage. It uses only
|
|
370
|
+
Drizzle's core query builder, so it works with every dialect and driver on
|
|
371
|
+
Drizzle 0.36+ and v1.
|
|
372
|
+
|
|
373
|
+
```ts
|
|
374
|
+
// server/db/schema.ts
|
|
375
|
+
import { pgTable, text, jsonb, timestamp, index } from 'drizzle-orm/pg-core'
|
|
376
|
+
|
|
377
|
+
export const filerFiles = pgTable('filer_files', {
|
|
378
|
+
id: text('id').primaryKey(),
|
|
379
|
+
groupId: text('group_id').notNull(),
|
|
380
|
+
metadata: jsonb('metadata'),
|
|
381
|
+
createdAt: timestamp('created_at'), // optional
|
|
382
|
+
updatedAt: timestamp('updated_at'), // optional
|
|
383
|
+
}, t => [index('filer_files_group_id_idx').on(t.groupId)])
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
// server/plugins/file-provider.ts (with `filer: { provider: 'custom' }`)
|
|
388
|
+
import { db } from '../utils/db'
|
|
389
|
+
import { filerFiles } from '../db/schema'
|
|
390
|
+
|
|
391
|
+
export default defineNitroPlugin(() => {
|
|
392
|
+
const { s3 } = useRuntimeConfig()
|
|
393
|
+
setFileStorageProvider(createDrizzleProvider({
|
|
394
|
+
db,
|
|
395
|
+
table: filerFiles,
|
|
396
|
+
blobs: createS3Client({
|
|
397
|
+
accessKeyId: s3.accessKeyId,
|
|
398
|
+
secretAccessKey: s3.secretAccessKey,
|
|
399
|
+
endpoint: s3.endpoint,
|
|
400
|
+
bucket: s3.bucket,
|
|
401
|
+
}),
|
|
402
|
+
// or keep bytes on disk in the module's fs storage: blobs: 'documents' (= `filer.storageName`)
|
|
403
|
+
// columns: { id: 'id', groupId: 'groupId', metadata: 'metadata', createdAt: 'createdAt', updatedAt: 'updatedAt' },
|
|
404
|
+
}))
|
|
405
|
+
})
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
- `columns` maps to the table's schema property names, so an existing table can be used. `createdAt`/`updatedAt` are filled in when the table has them.
|
|
409
|
+
- With a Postgres `jsonb` metadata column, `findByMeta()` (`@>`) and `update()` (`||` merge) run in the database — add a GIN index on `metadata` for large tables. Other column types and dialects filter and merge in JS.
|
|
410
|
+
- Bytes are stored at `<groupId>/data/<id>`, the same layout as the S3 and unstorage providers, so moving metadata into a database keeps existing files readable.
|
|
411
|
+
- Requires the optional `drizzle-orm` peer dependency.
|
|
412
|
+
|
|
413
|
+
### Migrating from the unstorage provider
|
|
414
|
+
|
|
415
|
+
Point `blobs` at the existing mount (`blobs: 'documents'`) so the bytes stay
|
|
416
|
+
where they are, then copy the metadata sidecars into the table once —
|
|
417
|
+
`importUnstorageMetadata` keeps ids and timestamps and skips rows that already
|
|
418
|
+
exist, so re-running it is safe:
|
|
419
|
+
|
|
420
|
+
```ts
|
|
421
|
+
// server/tasks/filer/import.ts (run with `nuxi task run filer:import`)
|
|
422
|
+
import { db } from '../../utils/db'
|
|
423
|
+
import { filerFiles } from '../../db/schema'
|
|
424
|
+
|
|
425
|
+
export default defineTask({
|
|
426
|
+
meta: { description: 'Copy nuxt-filer metadata into the database' },
|
|
427
|
+
async run() {
|
|
428
|
+
const result = await importUnstorageMetadata({ from: 'documents', db, table: filerFiles })
|
|
429
|
+
return { result } // { imported, skipped }
|
|
430
|
+
},
|
|
431
|
+
})
|
|
432
|
+
```
|
|
433
|
+
|
|
326
434
|
## Custom Provider
|
|
327
435
|
|
|
328
|
-
For advanced use cases (
|
|
436
|
+
For advanced use cases (other databases, external file sync), implement the `FileStorageProvider` interface and register it in a Nitro plugin:
|
|
329
437
|
|
|
330
438
|
```ts
|
|
331
439
|
// nuxt.config.ts
|
package/dist/module.d.mts
CHANGED
|
@@ -12,6 +12,25 @@ interface FilerImageOptions {
|
|
|
12
12
|
route?: string;
|
|
13
13
|
/** Name to register the @nuxt/image provider under. Default: `filer`. */
|
|
14
14
|
providerName?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Where images are processed. `'local'` (default) runs IPX/sharp in this
|
|
17
|
+
* server. `'imgproxy'` or `'ipx'` (a standalone `ipx serve`) offload it: the
|
|
18
|
+
* image route redirects to the service, and upload-time transforms are
|
|
19
|
+
* fetched from it — so `sharp`/`ipx` aren't needed here.
|
|
20
|
+
*/
|
|
21
|
+
service?: 'local' | 'imgproxy' | 'ipx';
|
|
22
|
+
/** Base URL of the image service. Runtime override: `NUXT_FILER_IMAGE_BASE_URL`. */
|
|
23
|
+
baseURL?: string;
|
|
24
|
+
/** imgproxy signing key (hex). Prefer `NUXT_FILER_IMAGE_KEY`. Unsigned URLs when unset. */
|
|
25
|
+
key?: string;
|
|
26
|
+
/** imgproxy signing salt (hex). Prefer `NUXT_FILER_IMAGE_SALT`. */
|
|
27
|
+
salt?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Origin the service fetches originals from (e.g. `http://app:3000` on a
|
|
30
|
+
* private network). Defaults to the request origin; required for
|
|
31
|
+
* upload-time transforms. Runtime override: `NUXT_FILER_IMAGE_SOURCE_URL`.
|
|
32
|
+
*/
|
|
33
|
+
sourceURL?: string;
|
|
15
34
|
}
|
|
16
35
|
interface FilerTusOptions {
|
|
17
36
|
/** Enable the tus endpoint + composable. Defaults to `true` when the `tus` option is an object. */
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -43,6 +43,18 @@ const module = defineNuxtModule({
|
|
|
43
43
|
{
|
|
44
44
|
name: "createS3Provider",
|
|
45
45
|
from: resolver.resolve("./runtime/server/providers/s3")
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "createS3Client",
|
|
49
|
+
from: resolver.resolve("./runtime/server/providers/s3")
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "createDrizzleProvider",
|
|
53
|
+
from: resolver.resolve("./runtime/server/providers/drizzle")
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "importUnstorageMetadata",
|
|
57
|
+
from: resolver.resolve("./runtime/server/providers/drizzle")
|
|
46
58
|
}
|
|
47
59
|
]);
|
|
48
60
|
const typesSpecifier = "nuxt-filer/runtime/types";
|
|
@@ -92,12 +104,29 @@ const module = defineNuxtModule({
|
|
|
92
104
|
const imageEnabled = options.image !== false && (imageOpt.enabled ?? true) !== false;
|
|
93
105
|
const ipxRoute = (imageOpt.route ?? "/_filer-ipx").replace(/\/+$/, "");
|
|
94
106
|
const providerName = imageOpt.providerName ?? "filer";
|
|
95
|
-
const
|
|
96
|
-
if (
|
|
107
|
+
const imageService = imageEnabled ? imageOpt.service ?? "local" : "local";
|
|
108
|
+
if (imageService !== "local") {
|
|
109
|
+
const runtimeFiler = nuxt.options.runtimeConfig.filer ?? {};
|
|
110
|
+
runtimeFiler.image = defu(runtimeFiler.image, {
|
|
111
|
+
baseURL: imageOpt.baseURL ?? "",
|
|
112
|
+
key: imageOpt.key ?? "",
|
|
113
|
+
salt: imageOpt.salt ?? "",
|
|
114
|
+
sourceURL: imageOpt.sourceURL ?? ""
|
|
115
|
+
});
|
|
116
|
+
nuxt.options.runtimeConfig.filer = runtimeFiler;
|
|
97
117
|
addServerHandler({
|
|
98
118
|
route: `${ipxRoute}/**`,
|
|
99
|
-
handler: resolver.resolve("./runtime/server/handlers/
|
|
119
|
+
handler: resolver.resolve("./runtime/server/handlers/image-service")
|
|
100
120
|
});
|
|
121
|
+
}
|
|
122
|
+
const shouldRegisterImage = imageEnabled && (imageOpt.enabled === "force" || hasNuxtModule("@nuxt/image"));
|
|
123
|
+
if (shouldRegisterImage) {
|
|
124
|
+
if (imageService === "local") {
|
|
125
|
+
addServerHandler({
|
|
126
|
+
route: `${ipxRoute}/**`,
|
|
127
|
+
handler: resolver.resolve("./runtime/server/handlers/ipx")
|
|
128
|
+
});
|
|
129
|
+
}
|
|
101
130
|
const optionsWithImage = nuxt.options;
|
|
102
131
|
const imageConfig = optionsWithImage.image ?? {};
|
|
103
132
|
const providers = imageConfig.providers ?? {};
|
|
@@ -123,18 +152,21 @@ const module = defineNuxtModule({
|
|
|
123
152
|
`export const storageName = ${JSON.stringify(options.storageName)};`,
|
|
124
153
|
`export const storagePath = ${JSON.stringify(options.storagePath)};`
|
|
125
154
|
].join("\n");
|
|
126
|
-
nitroConfig.virtual["#nuxt-filer-image"] =
|
|
155
|
+
nitroConfig.virtual["#nuxt-filer-image"] = [
|
|
156
|
+
`export const ipxRoute = ${JSON.stringify(ipxRoute)};`,
|
|
157
|
+
`export const imageService = ${JSON.stringify(imageService)};`
|
|
158
|
+
].join("\n");
|
|
127
159
|
nitroConfig.virtual["#nuxt-filer-tus"] = [
|
|
128
160
|
`export const tusRoute = ${JSON.stringify(tusRoute)};`,
|
|
129
161
|
`export const tusStagingDir = ${JSON.stringify(tusStagingDir)};`,
|
|
130
162
|
`export const tusMaxSize = ${JSON.stringify(tusOpt.maxSize ?? 0)};`,
|
|
131
163
|
`export const tusExpiration = ${JSON.stringify(tusOpt.expiration ?? 0)};`
|
|
132
164
|
].join("\n");
|
|
165
|
+
nitroConfig.plugins = nitroConfig.plugins || [];
|
|
166
|
+
nitroConfig.plugins.push(
|
|
167
|
+
resolver.resolve("./runtime/server/plugins/default-storage")
|
|
168
|
+
);
|
|
133
169
|
if (options.provider === "unstorage") {
|
|
134
|
-
nitroConfig.plugins = nitroConfig.plugins || [];
|
|
135
|
-
nitroConfig.plugins.push(
|
|
136
|
-
resolver.resolve("./runtime/server/plugins/default-storage")
|
|
137
|
-
);
|
|
138
170
|
nitroConfig.plugins.push(
|
|
139
171
|
resolver.resolve("./runtime/server/plugins/default-provider")
|
|
140
172
|
);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image route for an external service (imgproxy / standalone IPX). Same URL
|
|
3
|
+
* shape as the local IPX route — `<route>/<modifiers>/<groupId>/<id>` — so the
|
|
4
|
+
* `@nuxt/image` provider is unchanged:
|
|
5
|
+
*
|
|
6
|
+
* - `_` (no modifiers) serves the stored original. This is what the service
|
|
7
|
+
* fetches as its source.
|
|
8
|
+
* - anything else redirects to the service, so signing keys stay server-side
|
|
9
|
+
* and this server never loads sharp/IPX.
|
|
10
|
+
*/
|
|
11
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void | Buffer<ArrayBufferLike> | null>>;
|
|
12
|
+
export default _default;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineEventHandler, createError, getRequestURL, sendRedirect, setResponseHeader } from "h3";
|
|
2
|
+
import { ipxRoute } from "#nuxt-filer-image";
|
|
3
|
+
import { sendStoredFile } from "../utils/send.js";
|
|
4
|
+
import { imageServiceURL, parseModifiers, sourceURL } from "../utils/image-service.js";
|
|
5
|
+
import { useImageService } from "../utils/image-service-runtime.js";
|
|
6
|
+
export default defineEventHandler(async (event) => {
|
|
7
|
+
const path = event.path.slice(ipxRoute.length).split("?")[0].replace(/^\/+/, "");
|
|
8
|
+
const [modifiers = "", ...segments] = path.split("/");
|
|
9
|
+
const parts = segments.filter(Boolean).map(decodeURIComponent);
|
|
10
|
+
if (parts.length < 2) {
|
|
11
|
+
throw createError({ statusCode: 404, statusMessage: "Image not found" });
|
|
12
|
+
}
|
|
13
|
+
const id = parts.pop();
|
|
14
|
+
const groupId = parts.join("/");
|
|
15
|
+
if (modifiers === "_") return sendStoredFile(event, groupId, id);
|
|
16
|
+
const config = useImageService();
|
|
17
|
+
const origin = config.sourceURL ?? getRequestURL(event).origin;
|
|
18
|
+
const url = await imageServiceURL(config, parseModifiers(modifiers), sourceURL(config, origin, groupId, id));
|
|
19
|
+
setResponseHeader(event, "cache-control", "public, max-age=86400");
|
|
20
|
+
return sendRedirect(event, url, 302);
|
|
21
|
+
});
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
defineEventHandler,
|
|
3
|
+
sendWebResponse,
|
|
4
|
+
toWebRequest,
|
|
5
|
+
useBase
|
|
6
|
+
} from "h3";
|
|
7
|
+
import * as ipxModule from "ipx";
|
|
3
8
|
import { ipxRoute } from "#nuxt-filer-image";
|
|
4
9
|
import { useFileStorageProvider } from "../provider.js";
|
|
5
10
|
function parseId(id) {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
const trimmed = id.replace(/^\/+/, "");
|
|
12
|
+
const lastSlash = trimmed.lastIndexOf("/");
|
|
13
|
+
if (lastSlash <= 0 || lastSlash === trimmed.length - 1) return null;
|
|
14
|
+
return [trimmed.slice(0, lastSlash), trimmed.slice(lastSlash + 1)];
|
|
9
15
|
}
|
|
10
16
|
const filerStorage = {
|
|
11
17
|
name: "nuxt-filer",
|
|
@@ -18,7 +24,9 @@ const filerStorage = {
|
|
|
18
24
|
if (!file) return void 0;
|
|
19
25
|
const mtime = file.updatedAt ?? file.createdAt ?? /* @__PURE__ */ new Date();
|
|
20
26
|
return {
|
|
21
|
-
|
|
27
|
+
// HTTP dates have second precision; without truncating, the
|
|
28
|
+
// `if-modified-since` echo is always "older" than mtime and never 304s.
|
|
29
|
+
mtime: new Date(Math.floor(mtime.getTime() / 1e3) * 1e3),
|
|
22
30
|
maxAge: 60 * 60 * 24 * 365
|
|
23
31
|
};
|
|
24
32
|
},
|
|
@@ -31,13 +39,23 @@ const filerStorage = {
|
|
|
31
39
|
return data;
|
|
32
40
|
}
|
|
33
41
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
function createHandler() {
|
|
43
|
+
const ipx = ipxModule.createIPX({ storage: filerStorage });
|
|
44
|
+
const ipx4 = ipxModule;
|
|
45
|
+
if (ipx4.createIPXFetchHandler && ipx4.parseIPXURL) {
|
|
46
|
+
const { parseIPXURL } = ipx4;
|
|
47
|
+
const fetchHandler = ipx4.createIPXFetchHandler(ipx, {
|
|
48
|
+
parseURL(url) {
|
|
49
|
+
const parsed = new URL(url);
|
|
50
|
+
parsed.pathname = parsed.pathname.slice(ipxRoute.length) || "/";
|
|
51
|
+
return parseIPXURL(parsed.href);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
return defineEventHandler(
|
|
55
|
+
async (event) => sendWebResponse(event, await fetchHandler(toWebRequest(event)))
|
|
56
|
+
);
|
|
40
57
|
}
|
|
41
|
-
return
|
|
58
|
+
return useBase(ipxRoute, ipxModule.createIPXH3Handler(ipx));
|
|
42
59
|
}
|
|
43
|
-
|
|
60
|
+
let _handler = null;
|
|
61
|
+
export default defineEventHandler((event) => (_handler ??= createHandler())(event));
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Mount the default filesystem-backed storage for nuxt-filer. We mount
|
|
3
3
|
* via a Nitro plugin rather than `nitroConfig.storage` so that our
|
|
4
4
|
* custom fs driver is bundled with the plugin and there is no runtime
|
|
5
|
-
* module resolution against the package's `dist/`.
|
|
5
|
+
* module resolution against the package's `dist/`. A mount the app already
|
|
6
|
+
* configured under the same name (`nitro.storage`) is left alone.
|
|
6
7
|
*/
|
|
7
8
|
declare const _default: import("nitropack/types").NitroAppPlugin;
|
|
8
9
|
export default _default;
|
|
@@ -3,5 +3,7 @@ import { storageName, storagePath } from "#nuxt-filer-options";
|
|
|
3
3
|
import fsDriver from "../drivers/fs.js";
|
|
4
4
|
export default defineNitroPlugin(() => {
|
|
5
5
|
const storage = useStorage();
|
|
6
|
+
const mounted = storage.getMount(storageName).base.replace(/:$/, "");
|
|
7
|
+
if (mounted === storageName.replace(/[:/]+/g, ":").replace(/:$/, "")) return;
|
|
6
8
|
storage.mount(storageName, fsDriver({ base: storagePath }));
|
|
7
9
|
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Table } from 'drizzle-orm';
|
|
2
|
+
import type { FileStorageProvider } from '../../../runtime/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Where the drizzle provider keeps file bytes — the database only holds
|
|
5
|
+
* metadata. The {@link S3Client} from `createS3Client()` satisfies this, and a
|
|
6
|
+
* string is shorthand for a Nitro storage mount name.
|
|
7
|
+
*/
|
|
8
|
+
export interface BlobStore {
|
|
9
|
+
put(key: string, body: Buffer | Uint8Array, contentType?: string): Promise<void>;
|
|
10
|
+
get(key: string): Promise<Buffer | null>;
|
|
11
|
+
delete(key: string): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The slice of a Drizzle database the provider calls. Kept loose on purpose:
|
|
15
|
+
* Drizzle's per-dialect builders (pg/mysql/sqlite, v0.x and v1) don't share a
|
|
16
|
+
* common base type, so any `drizzle(...)` instance is accepted here.
|
|
17
|
+
*/
|
|
18
|
+
export interface DrizzleDatabase {
|
|
19
|
+
select: (...args: any[]) => any;
|
|
20
|
+
insert: (table: any) => any;
|
|
21
|
+
update: (table: any) => any;
|
|
22
|
+
delete: (table: any) => any;
|
|
23
|
+
}
|
|
24
|
+
export interface DrizzleProviderOptions {
|
|
25
|
+
/** Your Drizzle database instance (any dialect, any async or sync driver). */
|
|
26
|
+
db: DrizzleDatabase;
|
|
27
|
+
/** The table holding file metadata. */
|
|
28
|
+
table: Table;
|
|
29
|
+
/**
|
|
30
|
+
* Where file bytes are stored: a {@link BlobStore} (e.g. `createS3Client()`)
|
|
31
|
+
* or the name of a mounted Nitro storage.
|
|
32
|
+
*/
|
|
33
|
+
blobs: BlobStore | string;
|
|
34
|
+
/** Schema property names of the columns used. Unset timestamps are optional. */
|
|
35
|
+
columns?: {
|
|
36
|
+
/** Default: `'id'`. */
|
|
37
|
+
id?: string;
|
|
38
|
+
/** Default: `'groupId'`. */
|
|
39
|
+
groupId?: string;
|
|
40
|
+
/** JSON column. Default: `'metadata'`. */
|
|
41
|
+
metadata?: string;
|
|
42
|
+
/** Set on insert when the table has it. Default: `'createdAt'`. */
|
|
43
|
+
createdAt?: string;
|
|
44
|
+
/** Set on insert/update when the table has it. Default: `'updatedAt'`. */
|
|
45
|
+
updatedAt?: string;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Drizzle-backed {@link FileStorageProvider}: metadata lives in a database
|
|
50
|
+
* table, bytes in a {@link BlobStore}. Uses only Drizzle's core query builder,
|
|
51
|
+
* which is unchanged between v0.x and v1.
|
|
52
|
+
*
|
|
53
|
+
* When the metadata column is Postgres `jsonb`, `findByMeta` and `update` run
|
|
54
|
+
* in the database (`@>` containment / `||` merge); other dialects filter and
|
|
55
|
+
* merge in JS.
|
|
56
|
+
*/
|
|
57
|
+
export declare function createDrizzleProvider(options: DrizzleProviderOptions): FileStorageProvider;
|
|
58
|
+
export interface ImportUnstorageMetadataOptions {
|
|
59
|
+
/** Nitro storage mount the unstorage provider wrote to (e.g. `'documents'`). */
|
|
60
|
+
from: string;
|
|
61
|
+
db: DrizzleDatabase;
|
|
62
|
+
table: Table;
|
|
63
|
+
columns?: DrizzleProviderOptions['columns'];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* One-off migration from the unstorage provider: copies its JSON metadata
|
|
67
|
+
* sidecars (`<groupId>:meta:<id>`) into the Drizzle table, keeping ids and
|
|
68
|
+
* timestamps. Bytes stay where they are — point `createDrizzleProvider`'s
|
|
69
|
+
* `blobs` at the same mount. Rows that already exist are skipped, so it is
|
|
70
|
+
* safe to re-run.
|
|
71
|
+
*/
|
|
72
|
+
export declare function importUnstorageMetadata(options: ImportUnstorageMetadataOptions): Promise<{
|
|
73
|
+
imported: number;
|
|
74
|
+
skipped: number;
|
|
75
|
+
}>;
|