nuxt-feathers-zod 0.2.3 → 0.2.5

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
@@ -1,357 +1,193 @@
1
- # nuxt-feathers-zod
1
+ # nuxt-feathers-zod
2
+ [guide](https://vevedh.github.io/nuxt-feathers-zod/)
2
3
 
3
- Nuxt 4 module that embeds a **FeathersJS v5 (Dove)** server directly into **Nitro** and exposes Feathers services to your Nuxt application, with **Zod-first validation** and an optional **Swagger (legacy) integration**.
4
+ ### Guide officiel d’initialisation Nuxt 4 (Bun, Feathers v5, Zod)
4
5
 
5
- This module lets you run Feathers **without a separate backend process**, while keeping strong typing, shared schemas, and a clean DX.
6
+ Ce guide décrit **la seule procédure valide et supportée** pour initialiser correctement **nuxt-feathers-zod** dans un projet **Nuxt 4**, en se basant **strictement sur le comportement réel du module**.
6
7
 
7
- ---
8
+ Il évite volontairement toute “magie implicite” ou création manuelle non supportée.
8
9
 
9
- ## ✨ Features
10
+ ---
10
11
 
11
- - FeathersJS v5 (Dove) running **inside Nitro**
12
- - REST transport (Koa or Express)
13
- - Optional Socket.io transport (WebSocket)
14
- - **Zod schemas** for data + query validation (server-side)
15
- - Typed Feathers client injected into Nuxt (`$api`)
16
- - Optional Pinia integration via `feathers-pinia`
17
- - CLI to generate services and middleware
18
- - **Swagger UI (legacy)** support via `feathers-swagger`
12
+ ## 1. Objectif du module
19
13
 
20
- ---
14
+ `nuxt-feathers-zod` permet d’embarquer un **backend FeathersJS v5 (Dove)** directement dans **Nitro**, avec :
21
15
 
22
- ## 📦 Requirements
16
+ * API REST (`/feathers/*`)
17
+ * WebSocket (Socket.IO)
18
+ * Validation **Zod-first**
19
+ * Authentification **Local + JWT**
20
+ * Adapters (MongoDB, Memory, etc.)
21
+ * Swagger legacy (optionnel)
22
+ * Composables client (`useService`, `useAuth`, stores Pinia)
23
23
 
24
- - Node.js **18+** (or **Bun** recommended)
25
- - Nuxt **4**
26
- - FeathersJS **v5 (Dove)**
24
+ 👉 Il **n’y a pas de backend séparé** : Feathers est monté **dans Nuxt**.
27
25
 
28
26
  ---
29
27
 
30
- ## 🚀 Using nuxt-feathers-zod in a new Nuxt 4 project (step by step)
28
+ ## 2. Pré-requis
31
29
 
32
- This section explains **from zero** how to use `nuxt-feathers-zod` in a fresh Nuxt 4 project.
30
+ * **Bun** (recommandé et supporté)
31
+ * **Node.js ≥ 18**
32
+ * **Nuxt 4**
33
+ * MongoDB (optionnel mais recommandé)
33
34
 
34
35
  ---
35
36
 
36
- ## 1️⃣ Create a new Nuxt 4 project
37
+ ## 3. Création du projet Nuxt 4
37
38
 
38
39
  ```bash
39
- bunx nuxi@latest init my-nuxt-feathers-app
40
- cd my-nuxt-feathers-app
40
+ bunx nuxi@latest init my-site
41
+ cd my-site
41
42
  bun install
42
- ```
43
-
44
- Run once to verify:
45
-
46
- ```bash
47
43
  bun run dev
48
44
  ```
49
45
 
50
- ---
51
-
52
- ## 2️⃣ Install nuxt-feathers-zod
53
-
54
- ```bash
55
- bun add nuxt-feathers-zod feathers-pinia
56
- ```
57
-
58
- (Optional – for Swagger legacy support)
59
-
60
- ```bash
61
- bun add feathers-swagger
62
- ```
63
-
64
- ---
65
-
66
- ## 3️⃣ Enable the module
67
-
68
- ```ts
69
- // nuxt.config.ts
70
- export default defineNuxtConfig({
71
- modules: ['nuxt-feathers-zod'],
72
- })
73
- ```
74
-
75
- At this point, Nuxt will start with an **embedded FeathersJS server inside Nitro**.
76
-
77
- ---
78
-
79
- Parfait 👍
80
- Voici un **bloc prêt à intégrer dans ton `README.md`**, qui :
81
-
82
- 1. **explique précisément le rôle de `playground/server/feathers/dummy.ts`**
83
- 2. **explique pourquoi le premier service à créer doit être `users`**
84
- 3. **donne le raisonnement Feathers + Auth + Swagger + DX**
85
-
86
- Tu peux l’insérer tel quel (par exemple après la section _Minimal Feathers configuration_).
46
+ ➡️ Vérifie que Nuxt démarre **avant toute intégration Feathers**.
87
47
 
88
48
  ---
89
49
 
90
- ## 🧪 Playground rôle du fichier `playground/server/feathers/dummy.ts`
91
-
92
- Le fichier :
93
-
94
- ```ts
95
- playground / server / feathers / dummy.ts
96
- ```
97
-
98
- est **volontairement simple** et joue un rôle fondamental dans le module `nuxt-feathers-zod`.
99
-
100
- ### 🎯 Objectif principal
101
-
102
- 👉 **Fournir un backend Feathers minimal mais fonctionnel**, utilisable immédiatement dans le playground Nuxt, sans dépendre d’un vrai projet métier.
103
-
104
- Il permet de :
50
+ ## 4. Installation des dépendances
105
51
 
106
- - vérifier que **Feathers démarre correctement dans Nitro**
107
- - valider le **routing REST** (`/feathers/*`)
108
- - tester **Swagger UI**
109
- - servir de **service de test (smoke test)** pour le module
110
-
111
- ---
112
-
113
- ### 🧩 Que fait concrètement `dummy.ts` ?
114
-
115
- Typiquement, ce fichier :
116
-
117
- - crée une application Feathers
118
- - enregistre **au moins un service**
119
- - expose une route REST simple (ex: `/dummy`)
120
- - ne dépend pas d’authentification ni de base de données
121
-
122
- Exemple conceptuel :
123
-
124
- ```ts
125
- export function dummy(app: Application) {
126
- app.use('dummy', {
127
- async find() {
128
- return [{ ok: true }]
129
- },
130
- })
131
- }
132
- ```
133
-
134
- Ce service permet de tester immédiatement :
52
+ ### 4.1 Module principal
135
53
 
136
54
  ```bash
137
- curl http://localhost:3000/feathers/dummy
55
+ bun add nuxt-feathers-zod feathers-pinia
138
56
  ```
139
57
 
140
- ---
141
-
142
- ### 🧠 Pourquoi ce fichier est important ?
143
-
144
- Sans `dummy.ts` :
145
-
146
- - Feathers démarre **sans aucun service**
147
- - Swagger UI peut être vide ou trompeur
148
- - les tests d’intégration sont plus complexes
149
- - le playground ne démontre rien visuellement
58
+ ### 4.2 (Optionnel) Swagger legacy
150
59
 
151
- 👉 **`dummy.ts` est un point d’ancrage pédagogique et technique**, pas un service métier.
152
-
153
- ---
154
-
155
- ## 👤 Pourquoi créer immédiatement un service `users` (et pas un autre) ?
156
-
157
- Dans un projet Feathers **avec authentification**, le **premier vrai service à créer doit toujours être `users`**.
158
-
159
- Ce n’est **pas un choix arbitraire**.
160
-
161
- ---
162
-
163
- ### 🔐 Raison n°1 — Feathers Auth repose sur `users`
164
-
165
- Le système d’authentification Feathers (v5 Dove) repose sur :
166
-
167
- - le service **`authentication`**
168
- - une **stratégie locale ou JWT**
169
- - un **service utilisateur** (`users`)
170
-
171
- ➡️ **Sans service `users`**, ces endpoints ne peuvent pas fonctionner :
172
-
173
- ```http
174
- POST /feathers/authentication
175
- POST /feathers/users
60
+ ```bash
61
+ bun add feathers-swagger swagger-ui-dist
176
62
  ```
177
63
 
178
- ---
179
-
180
- ### 🔑 Raison n°2 — `users` est la source de vérité sécurité
181
-
182
- Le service `users` contient :
183
-
184
- - les identifiants (email, username, etc.)
185
- - le mot de passe hashé
186
- - les rôles (`admin`, `editor`, etc.)
187
- - les règles d’accès (RBAC)
188
-
189
- C’est sur `users` que reposent ensuite :
190
-
191
- - `authenticate('jwt')`
192
- - `requireRole(...)`
193
- - les hooks de sécurité
194
- - Swagger `securitySchemes`
195
-
196
- ---
197
-
198
- ### 🧠 Raison n°3 — Swagger dépend fortement de `users`
199
-
200
- Si tu actives Swagger (`feathers.swagger = true`) :
201
-
202
- - le **flux d’authentification JWT** est documenté
203
- - le bouton **Authorize** apparaît
204
- - les routes sécurisées sont visibles
205
-
206
- 👉 **Sans `users`, Swagger est incomplet ou trompeur**.
207
-
208
- ---
209
-
210
- ### 🧪 Raison n°4 — DX et tests automatisés
211
-
212
- Dans `nuxt-feathers-zod`, le service `users` permet :
213
-
214
- - de tester immédiatement :
215
-
216
- ```bash
217
- curl -X POST /feathers/users
218
- curl -X POST /feathers/authentication
219
- ```
220
-
221
- - de valider :
222
- - JWT
223
- - guards
224
- - hooks
225
- - swagger.json
226
-
227
- - d’avoir un **socle stable pour tous les autres services**
228
-
229
- ---
230
-
231
- ## ✅ Ordre recommandé des services dans un projet Nuxt + Feathers
232
-
233
- Toujours respecter cet ordre :
234
-
235
- 1. **`dummy`** (playground / smoke test)
236
- 2. **`users`** ← indispensable
237
- 3. `authentication` (auto-géré)
238
- 4. services métier (`articles`, `projects`, `tickets`, etc.)
64
+ > ⚠️ `swagger-ui-dist` est requis si `feathers.swagger = true`
239
65
 
240
66
  ---
241
67
 
242
- ## 🧭 Résumé
243
-
244
- - `dummy.ts` :
245
- - service minimal
246
- - sert de **preuve de fonctionnement**
247
- - facilite debug, Swagger et onboarding
248
-
249
- - `users` :
250
- - **service fondamental**
251
- - requis pour l’authentification
252
- - point central de la sécurité
253
- - base de Swagger, JWT et RBAC
254
-
255
- 👉 **Sans `users`, un projet Feathers n’est pas réellement exploitable.**
68
+ ## 5. Configuration **obligatoire** (`nuxt.config.ts`)
256
69
 
257
- ## 4️⃣ Minimal Feathers configuration
70
+ > ⚠️ **Cette configuration est critique**.
71
+ > Une mauvaise initialisation provoque des erreurs bloquantes au démarrage.
258
72
 
259
73
  ```ts
260
- // nuxt.config.ts
261
74
  export default defineNuxtConfig({
262
75
  modules: ['nuxt-feathers-zod'],
263
76
 
264
77
  feathers: {
78
+ /**
79
+ * RÈGLE D’OR :
80
+ * Le module scanne UNIQUEMENT ces dossiers
81
+ */
265
82
  servicesDirs: ['services'],
266
83
 
84
+ /**
85
+ * Transports
86
+ */
267
87
  transports: {
268
88
  rest: {
269
89
  path: '/feathers',
270
90
  framework: 'koa',
271
91
  },
92
+ websocket: true,
272
93
  },
273
94
 
95
+ /**
96
+ * Base de données (MongoDB recommandé)
97
+ */
274
98
  database: {
275
99
  mongo: {
276
- url: 'mongodb://127.0.0.1:27017/nuxt-feathers-zod',
100
+ url: 'mongodb://127.0.0.1:27017/my-site',
277
101
  },
278
102
  },
103
+
104
+ /**
105
+ * Authentification
106
+ */
107
+ auth: true,
108
+
109
+ /**
110
+ * Swagger legacy (optionnel)
111
+ */
112
+ swagger: false,
279
113
  },
280
114
  })
281
115
  ```
282
116
 
283
- Start Nuxt:
117
+ ---
284
118
 
285
- ```bash
286
- bun run dev
287
- ```
119
+ ## 6. RÈGLE FONDAMENTALE – À NE JAMAIS VIOLER
288
120
 
289
- Test:
121
+ > ❌ **Ne jamais créer un service manuellement**
122
+ > ✅ **Toujours utiliser la CLI officielle**
290
123
 
291
- ```bash
292
- curl http://localhost:3000/feathers
293
- ```
124
+ Cette règle est **imposée par le code interne du module**.
294
125
 
295
126
  ---
296
127
 
297
- ## 5️⃣ Generate your first service
128
+ ## 7. Création du premier service : `users` (OBLIGATOIRE)
298
129
 
299
130
  ```bash
300
- bunx nuxt-feathers-zod add service articles \
131
+ bunx nuxt-feathers-zod add service users \
301
132
  --adapter mongodb \
302
133
  --auth \
303
134
  --idField _id \
304
135
  --docs
305
136
  ```
306
137
 
307
- This generates:
138
+ ### Structure générée (attendue)
308
139
 
309
140
  ```
310
- services/articles/
311
- ├─ articles.class.ts
312
- ├─ articles.schema.ts
313
- ├─ articles.ts
314
- └─ articles.shared.ts
141
+ services/users/
142
+ users.ts
143
+ users.class.ts
144
+ users.schema.ts
145
+ users.shared.ts
315
146
  ```
316
147
 
317
- ---
148
+ ### Pourquoi `users` est obligatoire ?
318
149
 
319
- ## 6️⃣ Example: Articles service (server)
150
+ * Le module **résout l’authentification** via une `entityClass` nommée **`User`**
151
+ * Cette classe est **recherchée dynamiquement** dans les exports scannés
152
+ * Sans ce service :
320
153
 
321
- ```ts
322
- // services/articles/articles.ts
323
- export function articles(app: Application) {
324
- app.use('articles', new ArticlesService(getOptions(app)), {
325
- methods: ['find', 'get', 'create', 'patch', 'remove'],
326
- docs: {
327
- description: 'Articles API',
328
- idType: 'string',
329
- },
330
- })
331
- }
332
- ```
154
+ * `Services typeExports []`
155
+ * `Entity class User not found in services imports`
156
+ * **Boot impossible**
157
+
158
+ 👉 Le service `users` est la **clé de voûte** de tout projet `nuxt-feathers-zod`.
333
159
 
334
160
  ---
335
161
 
336
- ## 7️⃣ Use the service in Nuxt (client)
162
+ ## 8. Création d’un service métier (exemple : `articles`)
337
163
 
338
- ```vue
339
- <script setup lang="ts">
340
- const { $api } = useNuxtApp()
164
+ ```bash
165
+ bunx nuxt-feathers-zod add service articles \
166
+ --adapter mongodb \
167
+ --auth \
168
+ --idField _id \
169
+ --docs
170
+ ```
341
171
 
342
- const articles = await $api.service('articles').find()
343
- </script>
172
+ Structure :
344
173
 
345
- <template>
346
- <pre>{{ articles }}</pre>
347
- </template>
174
+ ```
175
+ services/articles/
176
+ articles.ts
177
+ articles.class.ts
178
+ articles.schema.ts
179
+ articles.shared.ts
348
180
  ```
349
181
 
350
182
  ---
351
183
 
352
- ## 8️⃣ Authentication example
184
+ ## 9. Démarrage et tests REST
185
+
186
+ ```bash
187
+ bun run dev
188
+ ```
353
189
 
354
- Create a user:
190
+ ### 9.1 Création d’un utilisateur
355
191
 
356
192
  ```bash
357
193
  curl -X POST http://localhost:3000/feathers/users \
@@ -359,7 +195,7 @@ curl -X POST http://localhost:3000/feathers/users \
359
195
  -d '{"userId":"demo","password":"demo123"}'
360
196
  ```
361
197
 
362
- Authenticate:
198
+ ### 9.2 Authentification
363
199
 
364
200
  ```bash
365
201
  curl -X POST http://localhost:3000/feathers/authentication \
@@ -367,7 +203,7 @@ curl -X POST http://localhost:3000/feathers/authentication \
367
203
  -d '{"strategy":"local","userId":"demo","password":"demo123"}'
368
204
  ```
369
205
 
370
- Use JWT:
206
+ ### 9.3 Accès à un service protégé
371
207
 
372
208
  ```bash
373
209
  curl http://localhost:3000/feathers/articles \
@@ -376,51 +212,101 @@ curl http://localhost:3000/feathers/articles \
376
212
 
377
213
  ---
378
214
 
379
- ## 9️⃣ Swagger (legacy) – complete example
215
+ ## 10. Swagger legacy (optionnel)
380
216
 
381
- Enable Swagger:
217
+ ### 10.1 Activer Swagger
382
218
 
383
219
  ```ts
384
- // nuxt.config.ts
385
- export default defineNuxtConfig({
386
- feathers: {
387
- swagger: true,
388
- },
389
- })
220
+ feathers: {
221
+ swagger: true,
222
+ }
390
223
  ```
391
224
 
392
- Access:
225
+ ### 10.2 Accès
393
226
 
394
- - UI: http://localhost:3000/feathers/docs/
395
- - Spec: http://localhost:3000/feathers/swagger.json
227
+ * UI :
228
+ `http://localhost:3000/feathers/docs/`
229
+ * Spec :
230
+ `http://localhost:3000/feathers/swagger.json`
231
+
232
+ ### ⚠️ Important
233
+
234
+ Dans l’UI Swagger, la spec doit être définie manuellement à :
235
+
236
+ ```
237
+ ../swagger.json
238
+ ```
396
239
 
397
- ⚠️ In Swagger UI, **replace `/swagger.json` with `../swagger.json`**.
240
+ (C’est un comportement connu et assumé du module.)
398
241
 
399
242
  ---
400
243
 
401
- ## 🔟 Project layout recap
244
+ ## 11. Plugins serveur Feathers (seed, hooks globaux)
402
245
 
246
+ ### Exemple : `server/feathers/dummy.ts`
247
+
248
+ ```ts
249
+ import { defineFeathersServerPlugin } from 'nuxt-feathers-zod/server'
250
+
251
+ export default defineFeathersServerPlugin((app) => {
252
+ app.hooks({
253
+ setup: [
254
+ async (context, next) => {
255
+ await context.app.service('users').create({
256
+ userId: 'admin',
257
+ password: 'admin123',
258
+ })
259
+ await next()
260
+ },
261
+ ],
262
+ })
263
+ })
403
264
  ```
404
- my-nuxt-feathers-app/
405
- ├─ app/
406
- ├─ server/
407
- ├─ services/
408
- │ └─ articles/
409
- ├─ nuxt.config.ts
410
- └─ package.json
411
- ```
265
+
266
+ ➡️ Ces fichiers sont des **plugins Feathers**, pas des services.
412
267
 
413
268
  ---
414
269
 
415
- ## 🧠 Credits
270
+ ## 12. Erreurs courantes et causes réelles
271
+
272
+ ### ❌ `Services typeExports []`
273
+
274
+ Causes :
275
+
276
+ * `servicesDirs` incorrect
277
+ * services créés manuellement
278
+ * fichiers mal nommés (`users.ts` manquant)
279
+
280
+ ### ❌ `Entity class User not found in services imports`
281
+
282
+ Cause exacte :
283
+
284
+ * le service `users` n’existe pas ou n’a pas été généré via la CLI
285
+
286
+ ✅ Solution universelle :
287
+
288
+ ```bash
289
+ bunx nuxt-feathers-zod add service users
290
+ ```
291
+
292
+ ---
416
293
 
417
- Inspired by:
294
+ ## 13. Bonnes pratiques figées
418
295
 
419
- - https://github.com/GaborTorma/feathers-nitro-adapter
420
- - FeathersJS v5 (Dove)
296
+ * ✅ **Toujours** générer les services avec la CLI
297
+ * `services/<name>/<name>.ts` obligatoire
298
+ * ✅ Zod-first (`*.schema.ts`)
299
+ * ❌ Pas de création manuelle
300
+ * ❌ Pas de renommage arbitraire de `User`
301
+ * ❌ Pas de déplacement hors `servicesDirs`
421
302
 
422
303
  ---
423
304
 
424
- ## Status
305
+ ## 14. Résumé express (checklist)
425
306
 
426
- **Stable reference version frozen**
307
+ 1. `bunx nuxi init`
308
+ 2. `bun add nuxt-feathers-zod feathers-pinia`
309
+ 3. `servicesDirs: ['services']`
310
+ 4. `bunx nuxt-feathers-zod add service users`
311
+ 5. `bunx nuxt-feathers-zod add service <business>`
312
+ 6. `bun run dev`
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^4.0.0"
6
6
  },
7
- "version": "0.2.3",
7
+ "version": "0.2.5",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -75,7 +75,7 @@ const module$1 = defineNuxtModule({
75
75
  const require = createRequire(import.meta.url);
76
76
  try {
77
77
  require.resolve("feathers-swagger", { paths: [nuxt.options.rootDir] });
78
- } catch {
78
+ } catch (e) {
79
79
  consola.warn(
80
80
  "feathers.swagger is enabled but 'feathers-swagger' could not be resolved from this Nuxt project. Install it in your app (root) dependencies: bun add feathers-swagger swagger-ui-dist"
81
81
  );
@@ -111,6 +111,12 @@ const module$1 = defineNuxtModule({
111
111
  addImports({ from: resolver.resolve("./runtime/stores/auth"), name: "useAuthStore" });
112
112
  addPlugin({ order: 1, src: resolver.resolve("./runtime/plugins/feathers-auth") });
113
113
  }
114
+ if (resolvedOptions.keycloak) {
115
+ addPlugin({ order: 1, src: resolver.resolve("./runtime/plugins/keycloak-sso"), mode: "client" });
116
+ } else if (resolvedOptions.auth) {
117
+ addImports({ from: resolver.resolve("./runtime/stores/auth"), name: "useAuthStore" });
118
+ addPlugin({ order: 1, src: resolver.resolve("./runtime/plugins/feathers-auth") });
119
+ }
114
120
  }
115
121
  let clientPluginDst;
116
122
  for (const clientTemplate of getClientTemplates(resolvedOptions, resolver)) {
@@ -118,7 +124,7 @@ const module$1 = defineNuxtModule({
118
124
  if (clientTemplate.filename?.endsWith("client/plugin.ts") || clientTemplate.filename?.endsWith("client/plugin"))
119
125
  clientPluginDst = tpl.dst;
120
126
  }
121
- addPlugin({ order: 0, src: clientPluginDst ?? resolver.resolve(resolvedOptions.templateDir, "client/plugin.ts"), mode: "client" });
127
+ addPlugin({ order: 0, src: clientPluginDst ?? resolver.resolve(resolvedOptions.templateDir, "client/plugin.ts") });
122
128
  }
123
129
  nuxt.hook("mcp:setup", ({ mcp }) => {
124
130
  mcp.tool("get-feathers-config", "Get the Feathers config", {}, async () => {
@@ -1 +1 @@
1
- export { createPiniaClient, defineGetters, defineSetters, defineValues, useAuth, useBackup, useDataStore, useInstanceDefaults, useServiceInstance, } from 'feathers-pinia';
1
+ export { createPiniaClient, defineGetters, defineSetters, defineValues, useAuth as usePiniaAuth, useBackup, useDataStore, useInstanceDefaults, useServiceInstance, } from 'feathers-pinia';
@@ -3,7 +3,7 @@ export {
3
3
  defineGetters,
4
4
  defineSetters,
5
5
  defineValues,
6
- useAuth,
6
+ useAuth as usePiniaAuth,
7
7
  useBackup,
8
8
  useDataStore,
9
9
  useInstanceDefaults,
@@ -0,0 +1,13 @@
1
+ type AuthProvider = 'keycloak' | 'local' | 'none';
2
+ export declare function useAuth(): {
3
+ provider: import("vue").ComputedRef<AuthProvider>;
4
+ ready: import("vue").Ref<boolean, boolean>;
5
+ isAuthenticated: import("vue").ComputedRef<boolean>;
6
+ user: import("vue").ComputedRef<any>;
7
+ permissions: import("vue").ComputedRef<any>;
8
+ token: import("vue").ComputedRef<string | null>;
9
+ init: () => Promise<void>;
10
+ login: (options?: any) => Promise<any>;
11
+ logout: (options?: any) => Promise<any>;
12
+ };
13
+ export {};
@@ -0,0 +1,99 @@
1
+ import { useNuxtApp, useRuntimeConfig } from "#imports";
2
+ import { computed, ref } from "vue";
3
+ import { useAuthStore } from "../stores/auth.js";
4
+ export function useAuth() {
5
+ const nuxtApp = useNuxtApp();
6
+ const rc = useRuntimeConfig();
7
+ const pub = rc.public;
8
+ const provider = computed(() => {
9
+ if (pub?._feathers?.keycloak)
10
+ return "keycloak";
11
+ if (pub?._feathers?.auth)
12
+ return "local";
13
+ return "none";
14
+ });
15
+ const ready = ref(false);
16
+ const keycloak = computed(() => nuxtApp.$keycloak);
17
+ const authStore = computed(() => {
18
+ try {
19
+ return useAuthStore();
20
+ } catch (e) {
21
+ return null;
22
+ }
23
+ });
24
+ const isAuthenticated = computed(() => {
25
+ if (provider.value === "keycloak")
26
+ return Boolean(keycloak.value?.authenticated);
27
+ if (provider.value === "local")
28
+ return Boolean(authStore.value?.authenticated || authStore.value?.isAuthenticated);
29
+ return false;
30
+ });
31
+ const user = computed(() => {
32
+ if (provider.value === "keycloak")
33
+ return keycloak.value?.user ?? null;
34
+ if (provider.value === "local")
35
+ return authStore.value?.user ?? null;
36
+ return null;
37
+ });
38
+ const permissions = computed(() => {
39
+ if (provider.value === "keycloak")
40
+ return keycloak.value?.permissions ?? [];
41
+ return [];
42
+ });
43
+ const token = computed(() => {
44
+ if (provider.value === "keycloak")
45
+ return keycloak.value?.token?.() ?? null;
46
+ if (provider.value === "local")
47
+ return authStore.value?.accessToken ?? null;
48
+ return null;
49
+ });
50
+ async function init() {
51
+ if (import.meta.server)
52
+ return;
53
+ if (ready.value) {
54
+ if (provider.value !== "keycloak")
55
+ return;
56
+ if (!isAuthenticated.value)
57
+ return;
58
+ if (user.value)
59
+ return;
60
+ }
61
+ if (provider.value === "keycloak") {
62
+ const kc = keycloak.value;
63
+ if (kc?.authenticated && typeof kc.whoami === "function") {
64
+ await kc.whoami().catch(() => null);
65
+ }
66
+ }
67
+ if (provider.value === "local") {
68
+ const s = authStore.value;
69
+ if (s?.restore) {
70
+ await s.restore().catch(() => {
71
+ });
72
+ }
73
+ }
74
+ ready.value = true;
75
+ }
76
+ async function login(options) {
77
+ if (provider.value === "keycloak")
78
+ return keycloak.value?.login?.(options);
79
+ if (provider.value === "local")
80
+ return authStore.value?.login?.(options);
81
+ }
82
+ async function logout(options) {
83
+ if (provider.value === "keycloak")
84
+ return keycloak.value?.logout?.(options);
85
+ if (provider.value === "local")
86
+ return authStore.value?.logout?.(options);
87
+ }
88
+ return {
89
+ provider,
90
+ ready,
91
+ isAuthenticated,
92
+ user,
93
+ permissions,
94
+ token,
95
+ init,
96
+ login,
97
+ logout
98
+ };
99
+ }
@@ -3,6 +3,7 @@ import type { AuthOptions, PublicAuthOptions, ResolvedAuthOptions, ResolvedAuthO
3
3
  import type { ClientOptions, ResolvedClientOptionsOrDisabled } from './client/index.js';
4
4
  import type { PiniaOptions } from './client/pinia.js';
5
5
  import type { DataBaseOptions, ResolvedDataBaseOptions } from './database/index.js';
6
+ import type { KeycloakOptions, ResolvedKeycloakOptions, ResolvedKeycloakOptionsOrDisabled } from './keycloak.js';
6
7
  import type { ResolvedServerOptions, ServerOptions } from './server.js';
7
8
  import type { ServicesDir, ServicesDirs } from './services.js';
8
9
  import type { ResolvedSwaggerOptionsOrDisabled, SwaggerOptionsOrDisabled } from './swagger.js';
@@ -14,6 +15,7 @@ export interface ModuleOptions {
14
15
  servicesDirs: ServicesDir | ServicesDirs;
15
16
  server: ServerOptions;
16
17
  auth: AuthOptions | boolean;
18
+ keycloak?: KeycloakOptions | boolean;
17
19
  client: ClientOptions | boolean;
18
20
  validator: ValidatorOptions;
19
21
  loadFeathersConfig: boolean;
@@ -26,6 +28,7 @@ export interface ResolvedOptions {
26
28
  servicesDirs: ServicesDirs;
27
29
  server: ResolvedServerOptions;
28
30
  auth: ResolvedAuthOptionsOrDisabled;
31
+ keycloak: ResolvedKeycloakOptionsOrDisabled;
29
32
  client: ResolvedClientOptionsOrDisabled;
30
33
  validator: ResolvedValidatorOptions;
31
34
  loadFeathersConfig: boolean;
@@ -33,11 +36,19 @@ export interface ResolvedOptions {
33
36
  }
34
37
  export interface FeathersRuntimeConfig {
35
38
  auth?: ResolvedAuthOptions;
39
+ keycloak?: ResolvedKeycloakOptions;
36
40
  }
37
41
  export interface FeathersPublicRuntimeConfig {
38
42
  transports: ResolvedTransportsOptions;
39
43
  auth?: PublicAuthOptions;
40
44
  pinia?: PiniaOptions;
45
+ keycloak?: {
46
+ serverUrl: string;
47
+ realm: string;
48
+ clientId: string;
49
+ authServicePath: string;
50
+ onLoad: 'check-sso' | 'login-required';
51
+ };
41
52
  }
42
53
  export type ModuleConfig = Partial<Omit<ModuleOptions, 'auth'> & {
43
54
  auth: Omit<AuthOptions, 'entityImport'> | boolean;
@@ -3,6 +3,7 @@ import { getServicesImports } from "../services.js";
3
3
  import { resolveAuthOptions } from "./authentication/index.js";
4
4
  import { resolveClientOptions } from "./client/index.js";
5
5
  import { resolveDataBaseOptions } from "./database/index.js";
6
+ import { resolveKeycloakOptions } from "./keycloak.js";
6
7
  import { resolveServerOptions } from "./server.js";
7
8
  import { resolveServicesDirs } from "./services.js";
8
9
  import { resolveSwaggerOptions } from "./swagger.js";
@@ -20,7 +21,8 @@ export async function resolveOptions(options, nuxt) {
20
21
  const validator = resolveValidatorOptions(options.validator);
21
22
  const swagger = resolveSwaggerOptions(options.swagger, transports);
22
23
  const servicesImports = await getServicesImports(servicesDirs);
23
- const auth = resolveAuthOptions(options.auth, !!client, servicesImports, appDir);
24
+ const keycloak = resolveKeycloakOptions(options.keycloak);
25
+ const auth = keycloak ? false : resolveAuthOptions(options.auth, !!client, servicesImports, appDir);
24
26
  const loadFeathersConfig = options.loadFeathersConfig;
25
27
  const resolvedOptions = {
26
28
  templateDir,
@@ -31,6 +33,7 @@ export async function resolveOptions(options, nuxt) {
31
33
  client,
32
34
  validator,
33
35
  auth,
36
+ keycloak,
34
37
  loadFeathersConfig,
35
38
  swagger
36
39
  };
@@ -42,6 +45,11 @@ export function resolveRuntimeConfig(options) {
42
45
  if (options.auth) {
43
46
  runtimeConfig.auth = options.auth;
44
47
  }
48
+ if (options.keycloak) {
49
+ runtimeConfig.keycloak = {
50
+ authServicePath: options.keycloak.authServicePath
51
+ };
52
+ }
45
53
  return runtimeConfig;
46
54
  }
47
55
  export function resolvePublicRuntimeConfig(options) {
@@ -62,5 +70,14 @@ export function resolvePublicRuntimeConfig(options) {
62
70
  if (client?.pinia) {
63
71
  publicRuntimeConfig.pinia = client.pinia;
64
72
  }
73
+ if (options.keycloak) {
74
+ publicRuntimeConfig.keycloak = {
75
+ serverUrl: options.keycloak.serverUrl,
76
+ realm: options.keycloak.realm,
77
+ clientId: options.keycloak.clientId,
78
+ authServicePath: options.keycloak.authServicePath,
79
+ onLoad: options.keycloak.onLoad
80
+ };
81
+ }
65
82
  return publicRuntimeConfig;
66
83
  }
@@ -0,0 +1,41 @@
1
+ export interface KeycloakOptions {
2
+ /**
3
+ * Keycloak base URL, e.g. https://sso.example.com
4
+ */
5
+ serverUrl: string;
6
+ realm: string;
7
+ clientId: string;
8
+ /**
9
+ * Keycloak init onLoad mode (client-side).
10
+ * - 'check-sso': do not force redirect, just check existing SSO session (recommended for Option A)
11
+ * - 'login-required': force login redirect if not authenticated
12
+ */
13
+ onLoad?: 'check-sso' | 'login-required';
14
+ /**
15
+ * Optional: client secret (only needed for UMA permissions flow)
16
+ */
17
+ secret?: string;
18
+ /**
19
+ * Optional: override issuer/audience checks.
20
+ * If omitted, issuer defaults to `${serverUrl}/realms/${realm}`
21
+ * and audience defaults to clientId.
22
+ */
23
+ issuer?: string;
24
+ audience?: string | string[];
25
+ /**
26
+ * Map Keycloak subject (`sub`) to a Feathers users service field.
27
+ */
28
+ userService?: string;
29
+ serviceIdField?: string;
30
+ /**
31
+ * Path for the bridge service (avoid '/auth' collisions).
32
+ */
33
+ authServicePath?: string;
34
+ /**
35
+ * Enable UMA permissions (requires secret).
36
+ */
37
+ permissions?: boolean;
38
+ }
39
+ export type ResolvedKeycloakOptions = Required<Pick<KeycloakOptions, 'serverUrl' | 'realm' | 'clientId' | 'userService' | 'serviceIdField' | 'authServicePath' | 'permissions' | 'onLoad'>> & Omit<KeycloakOptions, 'userService' | 'serviceIdField' | 'authServicePath' | 'permissions' | 'onLoad'>;
40
+ export type ResolvedKeycloakOptionsOrDisabled = ResolvedKeycloakOptions | false;
41
+ export declare function resolveKeycloakOptions(keycloak: KeycloakOptions | boolean | undefined): ResolvedKeycloakOptionsOrDisabled;
@@ -0,0 +1,19 @@
1
+ export function resolveKeycloakOptions(keycloak) {
2
+ if (!keycloak)
3
+ return false;
4
+ if (keycloak === true)
5
+ throw new Error("[nuxt-feathers-zod] feathers.keycloak=true is not supported; please provide an object config.");
6
+ return {
7
+ serverUrl: keycloak.serverUrl,
8
+ realm: keycloak.realm,
9
+ clientId: keycloak.clientId,
10
+ issuer: keycloak.issuer,
11
+ audience: keycloak.audience,
12
+ secret: keycloak.secret,
13
+ userService: keycloak.userService || "users",
14
+ serviceIdField: keycloak.serviceIdField || "keycloakId",
15
+ authServicePath: keycloak.authServicePath || "/_keycloak",
16
+ permissions: !!keycloak.permissions,
17
+ onLoad: keycloak.onLoad || "check-sso"
18
+ };
19
+ }
@@ -8,6 +8,6 @@ export default defineNuxtPlugin(async (nuxtApp) => {
8
8
  const auth = useAuthStore();
9
9
  try {
10
10
  await auth.reAuthenticate();
11
- } catch {
11
+ } catch (e) {
12
12
  }
13
13
  });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("#app").Plugin<Record<string, unknown>> & import("#app").ObjectPlugin<Record<string, unknown>>;
2
+ export default _default;
@@ -0,0 +1,147 @@
1
+ import { defineNuxtPlugin, useRuntimeConfig } from "#app";
2
+ import Keycloak from "keycloak-js";
3
+ export default defineNuxtPlugin(async (nuxtApp) => {
4
+ if (import.meta.server)
5
+ return;
6
+ const rc = useRuntimeConfig();
7
+ const pub = rc.public;
8
+ const kcPub = pub?._feathers?.keycloak ?? {};
9
+ const serverUrl = kcPub.serverUrl ?? pub.KC_URL;
10
+ const realm = kcPub.realm ?? pub.KC_REALM;
11
+ const clientId = kcPub.clientId ?? pub.KC_CLIENT_ID;
12
+ if (!serverUrl || !realm || !clientId) {
13
+ console.warn("[nuxt-feathers-zod][keycloak-sso] Missing Keycloak config. Expected feathers.keycloak.{serverUrl,realm,clientId} to be exposed in runtimeConfig.public._feathers.keycloak (or legacy KC_URL/KC_REALM/KC_CLIENT_ID).");
14
+ nuxtApp.provide("keycloak", {
15
+ instance: null,
16
+ authenticated: false,
17
+ token: () => void 0,
18
+ login: async () => {
19
+ },
20
+ logout: async () => {
21
+ },
22
+ updateToken: async () => false,
23
+ whoami: async () => null,
24
+ user: void 0,
25
+ permissions: []
26
+ });
27
+ return;
28
+ }
29
+ const keycloak = new Keycloak({
30
+ url: String(serverUrl),
31
+ realm: String(realm),
32
+ clientId: String(clientId)
33
+ });
34
+ const onLoad = pub?._feathers?.keycloak?.onLoad === "login-required" ? "login-required" : "check-sso";
35
+ const initResult = await keycloak.init({
36
+ onLoad,
37
+ checkLoginIframe: false,
38
+ silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`
39
+ }).catch(() => ({ authenticated: false }));
40
+ const authenticated = keycloak.authenticated === true && initResult?.authenticated !== false;
41
+ const userid = keycloak?.tokenParsed?.preferred_username;
42
+ const authServicePath = pub?._feathers?.keycloak?.authServicePath || "/_keycloak";
43
+ let refreshTimer;
44
+ let lastWhoamiAt = 0;
45
+ async function safeWhoami(reason) {
46
+ const now = Date.now();
47
+ if (now - lastWhoamiAt < 1e4)
48
+ return;
49
+ lastWhoamiAt = now;
50
+ try {
51
+ await whoami();
52
+ } catch (e) {
53
+ console.warn("[nuxt-feathers-zod][keycloak-sso] whoami refresh failed:", reason, e);
54
+ }
55
+ }
56
+ function startTokenRefreshLoop() {
57
+ if (refreshTimer)
58
+ window.clearInterval(refreshTimer);
59
+ const minValidity = 60;
60
+ const intervalMs = 3e4;
61
+ refreshTimer = window.setInterval(async () => {
62
+ try {
63
+ const refreshed = await keycloak.updateToken(minValidity);
64
+ if (refreshed)
65
+ await safeWhoami("updateToken(refreshed)");
66
+ } catch (e) {
67
+ console.warn("[nuxt-feathers-zod][keycloak-sso] updateToken failed:", e);
68
+ }
69
+ }, intervalMs);
70
+ nuxtApp.hook("app:beforeUnmount", () => {
71
+ if (refreshTimer)
72
+ window.clearInterval(refreshTimer);
73
+ });
74
+ }
75
+ const provided = {
76
+ instance: keycloak,
77
+ authenticated,
78
+ userid,
79
+ tokenParsed: keycloak.tokenParsed,
80
+ token: () => keycloak.token,
81
+ login: async (opts) => {
82
+ await keycloak.login(opts);
83
+ },
84
+ logout: async (opts) => {
85
+ await keycloak.logout(opts);
86
+ },
87
+ updateToken: async (minValidity = 30) => {
88
+ const refreshed = await keycloak.updateToken(minValidity);
89
+ if (refreshed)
90
+ await safeWhoami("provided.updateToken(refreshed)");
91
+ return refreshed;
92
+ },
93
+ // Fallback user (will be replaced by whoami() when $api is ready)
94
+ user: keycloak.tokenParsed ? { ...keycloak.tokenParsed, userid } : userid ? { userid } : void 0,
95
+ permissions: [],
96
+ whoami: async () => null
97
+ };
98
+ async function whoami() {
99
+ if (!provided.authenticated)
100
+ return null;
101
+ if (!("$api" in nuxtApp) || !nuxtApp.$api)
102
+ return null;
103
+ const token = keycloak.token;
104
+ if (!token)
105
+ return null;
106
+ try {
107
+ const res = await nuxtApp.$api.service(authServicePath).create({ access_token: token });
108
+ provided.user = res?.user;
109
+ provided.permissions = res?.permissions || [];
110
+ return res;
111
+ } catch (e) {
112
+ return null;
113
+ }
114
+ }
115
+ if ("$api" in nuxtApp && nuxtApp.$api) {
116
+ nuxtApp.$api.hooks({
117
+ before: [
118
+ async (ctx) => {
119
+ await keycloak.updateToken(30).catch(() => {
120
+ });
121
+ const t = keycloak.token;
122
+ if (!t)
123
+ return ctx;
124
+ ctx.params ||= {};
125
+ ctx.params.headers ||= {};
126
+ ctx.params.headers.Authorization = `Bearer ${t}`;
127
+ return ctx;
128
+ }
129
+ ]
130
+ });
131
+ }
132
+ provided.whoami = whoami;
133
+ nuxtApp.provide("keycloak", provided);
134
+ if (provided.authenticated) {
135
+ await safeWhoami("post-init");
136
+ keycloak.onTokenExpired = async () => {
137
+ try {
138
+ const refreshed = await keycloak.updateToken(60);
139
+ if (refreshed)
140
+ await safeWhoami("onTokenExpired(refreshed)");
141
+ } catch (e) {
142
+ console.warn("[nuxt-feathers-zod][keycloak-sso] token expired and refresh failed:", e);
143
+ }
144
+ };
145
+ startTokenRefreshLoop();
146
+ }
147
+ });
@@ -25,7 +25,7 @@ export const useAuthStore = defineStore("auth", () => {
25
25
  state.accessToken = result.accessToken;
26
26
  state.user = result.user;
27
27
  state.authenticated = true;
28
- } catch {
28
+ } catch (e) {
29
29
  logout();
30
30
  }
31
31
  }
@@ -1,4 +1,5 @@
1
1
  import { getServerAuthContents } from "./authentication.js";
2
+ import { getServerKeycloakContents } from "./keycloak.js";
2
3
  import { getServerMongodbContents } from "./mongodb.js";
3
4
  import { getServerPluginContents } from "./plugin.js";
4
5
  import { getServerContents } from "./server.js";
@@ -29,5 +30,12 @@ export function getServerTemplates(options) {
29
30
  write: true
30
31
  });
31
32
  }
33
+ if (options.keycloak) {
34
+ serverTemplates.push({
35
+ filename: "feathers/server/keycloak.ts",
36
+ getContents: getServerKeycloakContents(options),
37
+ write: true
38
+ });
39
+ }
32
40
  return serverTemplates;
33
41
  }
@@ -0,0 +1,2 @@
1
+ import type { ResolvedOptions } from '../../../runtime/options/index.js';
2
+ export declare function getServerKeycloakContents(options: ResolvedOptions): () => Promise<string>;
@@ -0,0 +1,96 @@
1
+ export function getServerKeycloakContents(options) {
2
+ return async () => {
3
+ return `// ! Generated by nuxt-feathers-zod - do not change manually
4
+ import type { HookContext, Application } from '@feathersjs/feathers'
5
+ import { createRemoteJWKSet, jwtVerify } from 'jose'
6
+
7
+ export type KeycloakConfig = {
8
+ serverUrl: string
9
+ realm: string
10
+ clientId: string
11
+ secret?: string
12
+ userService: string
13
+ serviceIdField: string
14
+ authServicePath: string
15
+ permissions?: boolean
16
+ }
17
+
18
+ function getBearer(headers?: Record<string, any>) {
19
+ const auth = headers?.authorization || headers?.Authorization
20
+ if (!auth || typeof auth !== 'string') return null
21
+ if (!auth.startsWith('Bearer ')) return null
22
+ return auth.slice(7)
23
+ }
24
+
25
+ async function safeResolveUser(app: Application, cfg: KeycloakConfig, payload: any) {
26
+ const sub = payload?.sub as string | undefined
27
+ if (!sub) return payload
28
+
29
+ try {
30
+ const users = app.service(cfg.userService)
31
+ const found = await users.find({ query: { [cfg.serviceIdField]: sub }, paginate: false } as any)
32
+ const first = Array.isArray(found) ? found[0] : (found?.data?.[0] ?? null)
33
+ if (first) return first
34
+
35
+ // Minimal create data to avoid schema conflicts
36
+ const created = await users.create({ [cfg.serviceIdField]: sub } as any)
37
+ return created
38
+ }
39
+ catch (e) {
40
+ // Fallback: no DB user service or schema mismatch -> return token payload as user
41
+ return { [cfg.serviceIdField]: sub, ...payload }
42
+ }
43
+ }
44
+
45
+ export function keycloakAuthorizationHook(app: Application, cfg: KeycloakConfig) {
46
+ const issuer = \`\${cfg.serverUrl}/realms/\${cfg.realm}\`
47
+ const jwks = createRemoteJWKSet(new URL(\`\${issuer}/protocol/openid-connect/certs\`))
48
+
49
+ return async (context: HookContext) => {
50
+ // Only external calls
51
+ if (!context.params?.provider) return context
52
+
53
+ const token = getBearer(context.params.headers as any)
54
+ if (!token) return context
55
+
56
+ const { payload } = await jwtVerify(token, jwks, {
57
+ issuer,
58
+ audience: cfg.clientId,
59
+ })
60
+
61
+ context.params.client = payload
62
+ context.params.user = await safeResolveUser(app, cfg, payload)
63
+ context.params.permissions = []
64
+
65
+ return context
66
+ }
67
+ }
68
+
69
+ export function keycloakBridgeService(app: Application, cfg: KeycloakConfig) {
70
+ return {
71
+ async create(data: { access_token: string }) {
72
+ const fakeCtx = {
73
+ app,
74
+ params: { headers: { Authorization: \`Bearer \${data.access_token}\` }, provider: 'rest' },
75
+ } as unknown as HookContext
76
+
77
+ await keycloakAuthorizationHook(app, cfg)(fakeCtx)
78
+
79
+ return {
80
+ user: (fakeCtx.params as any).user,
81
+ permissions: (fakeCtx.params as any).permissions,
82
+ }
83
+ },
84
+
85
+ async patch(_: any, data: { access_token: string }) {
86
+ return (this as any).create(data)
87
+ },
88
+
89
+ async remove() {
90
+ return { ok: true }
91
+ },
92
+ }
93
+ }
94
+ `;
95
+ };
96
+ }
@@ -23,6 +23,8 @@ export function getServerPluginContents(options) {
23
23
  const mongo = !!options.database?.mongo;
24
24
  const authStrategies = options?.auth?.authStrategies;
25
25
  const auth = (authStrategies || []).length > 0;
26
+ const keycloakEnabled = !!options.keycloak;
27
+ const keycloak = options.keycloak;
26
28
  const authService = options?.auth?.service;
27
29
  const restPath = transports?.rest?.path;
28
30
  const websocketPath = transports?.websocket?.path;
@@ -65,10 +67,27 @@ export function getServerPluginContents(options) {
65
67
  },
66
68
  ui: swagger.swaggerUI({ docsPath: '${docsPath}' }),
67
69
  }))
70
+ ` : "";
71
+ const keycloakInitBlock = keycloakEnabled ? ` // Init Keycloak-only SSO (global hook + bridge service)
72
+ const keycloakConfig = ${JSON.stringify({
73
+ serverUrl: keycloak.serverUrl,
74
+ realm: keycloak.realm,
75
+ clientId: keycloak.clientId,
76
+ issuer: keycloak.issuer || `${keycloak.serverUrl.replace(/\/$/, "")}/realms/${keycloak.realm}`,
77
+ audience: keycloak.audience || keycloak.clientId,
78
+ secret: keycloak.secret,
79
+ userService: keycloak.userService || "users",
80
+ serviceIdField: keycloak.serviceIdField || "keycloakId",
81
+ authServicePath: keycloak.authServicePath || "/_keycloak",
82
+ permissions: !!keycloak.permissions
83
+ })}
84
+ app.hooks({ before: [keycloakAuthorizationHook(app, keycloakConfig)] })
85
+ app.use(keycloakConfig.authServicePath, keycloakBridgeService(app, keycloakConfig) as any)
68
86
  ` : "";
69
87
  return `// ! Generated by nuxt-feathers-zod - do not change manually
70
88
  import type { NitroApp } from 'nitropack'
71
89
  import type { Application } from './server.js'
90
+ ${put(keycloakEnabled, `import { keycloakAuthorizationHook, keycloakBridgeService } from './keycloak.js'`)}
72
91
  ${put(options.loadFeathersConfig, `import configuration from '@feathersjs/configuration'`)}
73
92
  import { feathers } from '@feathersjs/feathers'
74
93
  ${puts([
@@ -94,6 +113,7 @@ ${put(options.loadFeathersConfig, `
94
113
  app.configure(configuration())
95
114
  `)}
96
115
  ${put(swaggerEnabled, swaggerInitBlock)}
116
+ ${put(keycloakEnabled, keycloakInitBlock)}
97
117
  // Add nitroApp to feathers app
98
118
  app.nitroApp = nitroApp;
99
119
  ${put(rest, `${put(koa, `
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-feathers-zod",
3
3
  "type": "module",
4
- "version": "0.2.3",
4
+ "version": "0.2.5",
5
5
  "packageManager": "bun@1.3.6",
6
6
  "description": "Feathers API integration for Nuxt",
7
7
  "author": "Herve de CHAVIGNY",
@@ -57,7 +57,10 @@
57
57
  "release": "bunx release-it",
58
58
  "release:patch": "bunx release-it patch",
59
59
  "release:minor": "bunx release-it minor",
60
- "release:major": "bunx release-it major"
60
+ "release:major": "bunx release-it major",
61
+ "docs:dev": "cd docs && bun install && bunx vitepress dev . --host",
62
+ "docs:build": "cd docs && bun install && bunx vitepress build .",
63
+ "docs:preview": "cd docs && bun install && bunx vitepress preview ."
61
64
  },
62
65
  "peerDependencies": {
63
66
  "nuxt": "^4.0.0"
@@ -85,6 +88,8 @@
85
88
  "consola": "latest",
86
89
  "defu": "latest",
87
90
  "feathers-pinia": "latest",
91
+ "jose": "^5.10.0",
92
+ "keycloak-js": "^24.0.5",
88
93
  "mongodb": "^6.21.0",
89
94
  "pinia": "latest",
90
95
  "socket.io-client": "latest",
@@ -105,7 +110,6 @@
105
110
  "dotenv-cli": "latest",
106
111
  "eslint": "^9.39.2",
107
112
  "nuxt": "^4.3.0",
108
- "nuxt-mcp": "latest",
109
113
  "release-it": "latest",
110
114
  "typescript": "latest",
111
115
  "vitest": "latest",
package/src/cli/index.ts CHANGED
@@ -261,7 +261,7 @@ async function ensureFeathersSwaggerSupport(projectRoot: string, io: { dry: bool
261
261
  )
262
262
  }
263
263
  }
264
- catch {
264
+ catch (e) {
265
265
  // ignore
266
266
  }
267
267
  }
@@ -319,7 +319,7 @@ function relativeToCwd(p: string) {
319
319
  try {
320
320
  return p.replace(`${resolve(process.cwd())}/`, '')
321
321
  }
322
- catch {
322
+ catch (e) {
323
323
  return p
324
324
  }
325
325
  }