@lenne.tech/nuxt-extensions 1.18.5 → 1.19.0
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 +17 -3
- package/dist/module.d.mts +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +10 -3
- package/dist/runtime/composables/use-lt-file.js +2 -1
- package/dist/runtime/composables/use-lt-tus-upload.d.ts +2 -2
- package/dist/runtime/composables/use-lt-tus-upload.js +16 -2
- package/dist/runtime/types/module.d.ts +16 -1
- package/dist/runtime/types/upload.d.ts +9 -2
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -177,8 +177,9 @@ export default defineNuxtConfig({
|
|
|
177
177
|
|
|
178
178
|
// TUS upload configuration
|
|
179
179
|
tus: {
|
|
180
|
-
defaultEndpoint: '/files/upload'
|
|
180
|
+
defaultEndpoint: '/tus', // nest-server's TUS path (module default: '/files/upload')
|
|
181
181
|
defaultChunkSize: 5 * 1024 * 1024, // 5MB
|
|
182
|
+
resolveAgainstApi: true, // relative endpoint = path on the API, like auth/AI requests
|
|
182
183
|
},
|
|
183
184
|
|
|
184
185
|
// i18n configuration (optional)
|
|
@@ -536,8 +537,8 @@ const {
|
|
|
536
537
|
resumeUpload,
|
|
537
538
|
cancelUpload,
|
|
538
539
|
} = useLtTusUpload({
|
|
539
|
-
endpoint: '/
|
|
540
|
-
onSuccess: (item) => console.log('
|
|
540
|
+
endpoint: '/tus', // optional — defaults to ltExtensions.tus.defaultEndpoint
|
|
541
|
+
onSuccess: (item) => console.log('Stored file:', item.fileId),
|
|
541
542
|
onError: (item, error) => console.error('Failed:', error),
|
|
542
543
|
});
|
|
543
544
|
|
|
@@ -575,6 +576,19 @@ function handleFileSelect(event: Event) {
|
|
|
575
576
|
</template>
|
|
576
577
|
```
|
|
577
578
|
|
|
579
|
+
**Where the upload goes.** A relative endpoint is a path on the API: it is resolved like the auth
|
|
580
|
+
requests — `NUXT_PUBLIC_API_URL` + path, or `/api` + path while the dev proxy is on
|
|
581
|
+
(`NUXT_PUBLIC_API_PROXY=true`). So `'/tus'` works in every environment without per-stage config. An
|
|
582
|
+
absolute URL is used as it is. If your project routes uploads through its own same-origin proxy
|
|
583
|
+
instead, set `ltExtensions.tus.resolveAgainstApi: false` and the relative endpoint stays on the app
|
|
584
|
+
origin.
|
|
585
|
+
|
|
586
|
+
**Which id to keep.** After a successful upload, `item.fileId` is the id of the stored file, read
|
|
587
|
+
from the `X-File-Id` response header (nest-server 11.42.13+). Use it for file links and references.
|
|
588
|
+
`item.url` is the TUS upload resource; its last segment is the upload id, not the file id. `fileId`
|
|
589
|
+
is undefined when the backend sends no such header (an older nest-server, or an `onUploadComplete()`
|
|
590
|
+
override that never calls `super.onUploadComplete(upload)`).
|
|
591
|
+
|
|
578
592
|
### Transition Components
|
|
579
593
|
|
|
580
594
|
```vue
|
package/dist/module.d.mts
CHANGED
|
@@ -3,7 +3,7 @@ import { LtExtensionsModuleOptions } from '../dist/runtime/types/index.js';
|
|
|
3
3
|
export * from '../dist/runtime/types/index.js';
|
|
4
4
|
|
|
5
5
|
declare const name = "@lenne.tech/nuxt-extensions";
|
|
6
|
-
declare const version = "1.
|
|
6
|
+
declare const version = "1.19.0";
|
|
7
7
|
declare const configKey = "ltExtensions";
|
|
8
8
|
declare const DEFAULT_LT_AUTH_STATE_COOKIE = "lt-auth-state";
|
|
9
9
|
declare const DEFAULT_LT_JWT_TOKEN_COOKIE = "lt-jwt-token";
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { defineNuxtModule, createResolver, tryResolveModule, addImports, addComp
|
|
|
2
2
|
import { pathToFileURL } from 'node:url';
|
|
3
3
|
|
|
4
4
|
const name = "@lenne.tech/nuxt-extensions";
|
|
5
|
-
const version = "1.
|
|
5
|
+
const version = "1.19.0";
|
|
6
6
|
const configKey = "ltExtensions";
|
|
7
7
|
const DEFAULT_LT_AUTH_STATE_COOKIE = "lt-auth-state";
|
|
8
8
|
const DEFAULT_LT_JWT_TOKEN_COOKIE = "lt-jwt-token";
|
|
@@ -49,7 +49,8 @@ const defaultOptions = {
|
|
|
49
49
|
tus: {
|
|
50
50
|
defaultChunkSize: 5 * 1024 * 1024,
|
|
51
51
|
// 5MB
|
|
52
|
-
defaultEndpoint: "/files/upload"
|
|
52
|
+
defaultEndpoint: "/files/upload",
|
|
53
|
+
resolveAgainstApi: true
|
|
53
54
|
}
|
|
54
55
|
};
|
|
55
56
|
const module$1 = defineNuxtModule({
|
|
@@ -147,7 +148,8 @@ const module$1 = defineNuxtModule({
|
|
|
147
148
|
},
|
|
148
149
|
tus: {
|
|
149
150
|
defaultChunkSize: resolvedOptions.tus?.defaultChunkSize || 5 * 1024 * 1024,
|
|
150
|
-
defaultEndpoint: resolvedOptions.tus?.defaultEndpoint || "/files/upload"
|
|
151
|
+
defaultEndpoint: resolvedOptions.tus?.defaultEndpoint || "/files/upload",
|
|
152
|
+
resolveAgainstApi: resolvedOptions.tus?.resolveAgainstApi ?? true
|
|
151
153
|
}
|
|
152
154
|
};
|
|
153
155
|
addImports([
|
|
@@ -241,6 +243,11 @@ const module$1 = defineNuxtModule({
|
|
|
241
243
|
}
|
|
242
244
|
nuxt.options.build.transpile.push(resolve("./runtime"));
|
|
243
245
|
nuxt.options.build.transpile.push("tus-js-client");
|
|
246
|
+
if (await tryResolveModule("tus-js-client", [pathToFileURL(`${nuxt.options.rootDir}/`)])) {
|
|
247
|
+
nuxt.options.vite.optimizeDeps ||= {};
|
|
248
|
+
nuxt.options.vite.optimizeDeps.include ||= [];
|
|
249
|
+
nuxt.options.vite.optimizeDeps.include.push("tus-js-client > url-parse");
|
|
250
|
+
}
|
|
244
251
|
console.log(`[${name}] v${version}`);
|
|
245
252
|
if (nuxt.options.dev) {
|
|
246
253
|
console.log(`[${name}] Module loaded with config:`, {
|
|
@@ -2,7 +2,8 @@ import { useRuntimeConfig } from "#imports";
|
|
|
2
2
|
export function useLtFile() {
|
|
3
3
|
const config = useRuntimeConfig();
|
|
4
4
|
function getFileApiBase() {
|
|
5
|
-
|
|
5
|
+
const endpoint = config.public.ltExtensions?.tus?.defaultEndpoint;
|
|
6
|
+
return (endpoint?.endsWith("/upload") ? endpoint.slice(0, -"/upload".length) : "") || "/files";
|
|
6
7
|
}
|
|
7
8
|
function isValidMongoID(id) {
|
|
8
9
|
return /^[a-f\d]{24}$/i.test(id);
|
|
@@ -16,8 +16,8 @@ import type { LtUploadOptions, UseLtTusUploadReturn } from '../types/index.js';
|
|
|
16
16
|
* @example
|
|
17
17
|
* ```typescript
|
|
18
18
|
* const { addFiles, uploads, totalProgress, isUploading } = useLtTusUpload({
|
|
19
|
-
* endpoint: '/
|
|
20
|
-
* onSuccess: (item) => console.log('
|
|
19
|
+
* endpoint: '/tus', // a path on the API — resolved like the auth requests
|
|
20
|
+
* onSuccess: (item) => console.log('Stored file:', item.fileId),
|
|
21
21
|
* });
|
|
22
22
|
*
|
|
23
23
|
* // Add files from input
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { computed, ref, useRuntimeConfig } from "#imports";
|
|
2
|
+
import { buildLtApiUrl } from "../lib/auth-state.js";
|
|
3
|
+
const LT_TUS_FILE_ID_HEADER = "X-File-Id";
|
|
4
|
+
const ABSOLUTE_URL = /^(?:[a-z][a-z\d+.-]*:|\/\/)/i;
|
|
5
|
+
function resolveLtTusEndpoint(endpoint, resolveAgainstApi) {
|
|
6
|
+
if (!endpoint || !resolveAgainstApi || ABSOLUTE_URL.test(endpoint)) {
|
|
7
|
+
return endpoint;
|
|
8
|
+
}
|
|
9
|
+
return buildLtApiUrl(endpoint.startsWith("/") ? endpoint : `/${endpoint}`);
|
|
10
|
+
}
|
|
2
11
|
let tusModule = null;
|
|
3
12
|
async function getTusModule() {
|
|
4
13
|
if (!tusModule) {
|
|
@@ -22,7 +31,7 @@ export function useLtTusUpload(defaultOptions = {}) {
|
|
|
22
31
|
endpoint: tusConfig?.defaultEndpoint || "/files/upload",
|
|
23
32
|
parallelUploads: 3,
|
|
24
33
|
retryDelays: [0, 1e3, 3e3, 5e3, 1e4],
|
|
25
|
-
...defaultOptions
|
|
34
|
+
...Object.fromEntries(Object.entries(defaultOptions).filter(([, value]) => value !== void 0))
|
|
26
35
|
};
|
|
27
36
|
const uploads = computed(() => Array.from(uploadItems.value.values()));
|
|
28
37
|
const isUploading = computed(() => uploads.value.some((u) => u.status === "uploading"));
|
|
@@ -73,9 +82,10 @@ export function useLtTusUpload(defaultOptions = {}) {
|
|
|
73
82
|
async function createTusUpload(item, options) {
|
|
74
83
|
const tus = await getTusModule();
|
|
75
84
|
const speedTracker = createSpeedTracker();
|
|
85
|
+
let fileId;
|
|
76
86
|
return new tus.Upload(item.file, {
|
|
77
87
|
chunkSize: options.chunkSize || defaultConfig.chunkSize,
|
|
78
|
-
endpoint: options.endpoint || defaultConfig.endpoint,
|
|
88
|
+
endpoint: resolveLtTusEndpoint(options.endpoint || defaultConfig.endpoint, tusConfig?.resolveAgainstApi !== false),
|
|
79
89
|
headers: options.headers,
|
|
80
90
|
metadata: {
|
|
81
91
|
filename: item.file.name,
|
|
@@ -83,6 +93,9 @@ export function useLtTusUpload(defaultOptions = {}) {
|
|
|
83
93
|
...options.metadata,
|
|
84
94
|
...item.metadata
|
|
85
95
|
},
|
|
96
|
+
onAfterResponse: (_req, res) => {
|
|
97
|
+
fileId = res.getHeader(LT_TUS_FILE_ID_HEADER) || fileId;
|
|
98
|
+
},
|
|
86
99
|
onBeforeRequest: (req) => {
|
|
87
100
|
const xhr = req.getUnderlyingObject();
|
|
88
101
|
xhr.withCredentials = true;
|
|
@@ -115,6 +128,7 @@ export function useLtTusUpload(defaultOptions = {}) {
|
|
|
115
128
|
const currentItem = uploadItems.value.get(item.id);
|
|
116
129
|
updateItem(item.id, {
|
|
117
130
|
completedAt: /* @__PURE__ */ new Date(),
|
|
131
|
+
fileId,
|
|
118
132
|
progress: { ...currentItem.progress, percentage: 100 },
|
|
119
133
|
status: "completed",
|
|
120
134
|
url: tusUpload?.url ?? void 0
|
|
@@ -88,8 +88,22 @@ export interface LtAuthModuleOptions {
|
|
|
88
88
|
export interface LtTusModuleOptions {
|
|
89
89
|
/** Default chunk size in bytes (default: 5MB) */
|
|
90
90
|
defaultChunkSize?: number;
|
|
91
|
-
/**
|
|
91
|
+
/**
|
|
92
|
+
* Default TUS upload endpoint (default: '/files/upload').
|
|
93
|
+
*
|
|
94
|
+
* nest-server serves TUS at `/tus` unless its `tus.path` is changed. A relative value is a path
|
|
95
|
+
* on the API while `resolveAgainstApi` is on; an absolute URL is used as it is.
|
|
96
|
+
*/
|
|
92
97
|
defaultEndpoint?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Resolve a relative upload endpoint against the API base URL (default: true).
|
|
100
|
+
*
|
|
101
|
+
* Uses the same rule as the auth and AI requests: `NUXT_PUBLIC_API_URL` on the client, the
|
|
102
|
+
* `/api` dev-proxy prefix while `NUXT_PUBLIC_API_PROXY` is on. Without it, a relative endpoint
|
|
103
|
+
* goes to the app origin, where nothing serves TUS unless the project put its own proxy in
|
|
104
|
+
* front. Set it to `false` for exactly that setup. Absolute endpoints are never rewritten.
|
|
105
|
+
*/
|
|
106
|
+
resolveAgainstApi?: boolean;
|
|
93
107
|
}
|
|
94
108
|
/**
|
|
95
109
|
* i18n module configuration options
|
|
@@ -301,6 +315,7 @@ export interface LtExtensionsPublicRuntimeConfig {
|
|
|
301
315
|
tus: {
|
|
302
316
|
defaultChunkSize: number;
|
|
303
317
|
defaultEndpoint: string;
|
|
318
|
+
resolveAgainstApi: boolean;
|
|
304
319
|
};
|
|
305
320
|
};
|
|
306
321
|
}
|
|
@@ -28,6 +28,13 @@ export interface LtUploadItem {
|
|
|
28
28
|
error?: string;
|
|
29
29
|
/** The file being uploaded */
|
|
30
30
|
file: File;
|
|
31
|
+
/**
|
|
32
|
+
* Id of the stored file, from the `X-File-Id` header of the response that completed the upload
|
|
33
|
+
* (nest-server 11.42.13+). Use it for file links and references — not the last segment of `url`,
|
|
34
|
+
* which is the TUS upload id. Undefined when the backend sends no such header: an older
|
|
35
|
+
* nest-server, or an `onUploadComplete()` override that never calls `super.onUploadComplete()`.
|
|
36
|
+
*/
|
|
37
|
+
fileId?: string;
|
|
31
38
|
/** Unique identifier for this upload */
|
|
32
39
|
id: string;
|
|
33
40
|
/** Custom metadata to include with the upload */
|
|
@@ -38,7 +45,7 @@ export interface LtUploadItem {
|
|
|
38
45
|
startedAt?: Date;
|
|
39
46
|
/** Current status of the upload */
|
|
40
47
|
status: LtUploadStatus;
|
|
41
|
-
/** URL of the
|
|
48
|
+
/** URL of the TUS upload resource (available after completion) — not a file URL; see `fileId` */
|
|
42
49
|
url?: string;
|
|
43
50
|
}
|
|
44
51
|
/**
|
|
@@ -49,7 +56,7 @@ export interface LtUploadOptions {
|
|
|
49
56
|
autoStart?: boolean;
|
|
50
57
|
/** Chunk size in bytes (default: 5MB) */
|
|
51
58
|
chunkSize?: number;
|
|
52
|
-
/** TUS upload endpoint URL */
|
|
59
|
+
/** TUS upload endpoint URL; a relative path is resolved against the API (see `tus.resolveAgainstApi`) */
|
|
53
60
|
endpoint?: string;
|
|
54
61
|
/** Additional headers to include with requests */
|
|
55
62
|
headers?: Record<string, string>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nuxt-extensions",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"description": "Reusable Nuxt 4 composables, components, and Better-Auth integration for lenne.tech projects",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -102,23 +102,23 @@
|
|
|
102
102
|
"devDependencies": {
|
|
103
103
|
"@better-auth/passkey": "1.7.7",
|
|
104
104
|
"@nuxt/module-builder": "1.0.3",
|
|
105
|
-
"@nuxt/schema": "4.6.
|
|
105
|
+
"@nuxt/schema": "4.6.1",
|
|
106
106
|
"@nuxt/ui": "4.11.3",
|
|
107
|
-
"@playwright/test": "1.
|
|
108
|
-
"@types/node": "26.6.
|
|
107
|
+
"@playwright/test": "1.64.0",
|
|
108
|
+
"@types/node": "26.6.5",
|
|
109
109
|
"@vitest/coverage-v8": "5.0.3",
|
|
110
110
|
"@vue/server-renderer": "3.5.43",
|
|
111
111
|
"better-auth": "1.7.7",
|
|
112
112
|
"cross-spawn": "7.0.6",
|
|
113
|
-
"happy-dom": "20.14.
|
|
114
|
-
"nuxt": "4.6.
|
|
113
|
+
"happy-dom": "20.14.6",
|
|
114
|
+
"nuxt": "4.6.1",
|
|
115
115
|
"oxfmt": "0.72.0",
|
|
116
116
|
"oxlint": "1.87.0",
|
|
117
117
|
"reka-ui": "2.10.5",
|
|
118
118
|
"tus-js-client": "4.3.1",
|
|
119
119
|
"typescript": "5.9.3",
|
|
120
120
|
"vitest": "5.0.3",
|
|
121
|
-
"vue-tsc": "3.3.
|
|
121
|
+
"vue-tsc": "3.3.13"
|
|
122
122
|
},
|
|
123
123
|
"keywords": [
|
|
124
124
|
"nuxt",
|
|
@@ -137,5 +137,5 @@
|
|
|
137
137
|
"node": ">= 22",
|
|
138
138
|
"pnpm": "^11.0.0"
|
|
139
139
|
},
|
|
140
|
-
"packageManager": "pnpm@11.
|
|
140
|
+
"packageManager": "pnpm@11.28.5+sha512.484563d30b4c2ad27cb48aa94ff2b302dbcebc8ff6bf28816c2589886e146ce7f76f354f3bc1a438e6f5e9768c302aa0a6f7add8232903cd4cf81426cbc14d9e"
|
|
141
141
|
}
|