@lenne.tech/nuxt-extensions 1.18.6 → 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 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: '/api/files/upload',
540
- onSuccess: (item) => console.log('Uploaded:', item.url),
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.18.6";
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "configKey": "ltExtensions",
3
3
  "name": "@lenne.tech/nuxt-extensions",
4
- "version": "1.18.6",
4
+ "version": "1.19.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.3",
7
7
  "unbuild": "3.6.1"
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.18.6";
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
- return config.public.ltExtensions?.tus?.defaultEndpoint?.replace("/upload", "") || "/files";
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: '/api/files/upload',
20
- * onSuccess: (item) => console.log('Uploaded:', item.url),
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
- /** Default TUS upload endpoint (default: '/files/upload') */
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 uploaded file (available after completion) */
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.18.6",
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.0",
105
+ "@nuxt/schema": "4.6.1",
106
106
  "@nuxt/ui": "4.11.3",
107
107
  "@playwright/test": "1.64.0",
108
- "@types/node": "26.6.4",
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.5",
114
- "nuxt": "4.6.0",
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.12"
121
+ "vue-tsc": "3.3.13"
122
122
  },
123
123
  "keywords": [
124
124
  "nuxt",