diff --git a/_artifacts/domain_map.yaml b/_artifacts/domain_map.yaml
index 318aa7c9..8cfb9fd0 100644
--- a/_artifacts/domain_map.yaml
+++ b/_artifacts/domain_map.yaml
@@ -34,7 +34,7 @@ domains:
slug: server
description: >
Per-request setupMiddleware and checkMiddleware for Express, Hono, Fastify,
- tRPC, oRPC, Node, and Elysia.
+ NestJS, tRPC, oRPC, Node, and Elysia.
- name: 'SSR and hydration'
slug: ssr
@@ -78,7 +78,7 @@ skills:
~all/~any, entity-aware ReBAC rules, isReady/isReadyAsync;
PermixProvider/usePermix/createComponents and SSR dehydrate/hydrate for
React, Vue, Solid, Svelte, Next.js, TanStack Start; setupMiddleware and
- checkMiddleware for Express, Hono, Fastify, tRPC, oRPC, Node, Elysia.
+ checkMiddleware for Express, Hono, Fastify, NestJS, tRPC, oRPC, Node, Elysia.
Single skill with a thin SKILL.md router and three reference files
loaded on demand.
type: core
@@ -94,6 +94,7 @@ skills:
- express
- hono
- fastify
+ - nest
- trpc
- orpc
- node
@@ -140,6 +141,7 @@ skills:
- 'letstri/permix:docs/content/docs/integrations/express.mdx'
- 'letstri/permix:docs/content/docs/integrations/hono.mdx'
- 'letstri/permix:docs/content/docs/integrations/fastify.mdx'
+ - 'letstri/permix:docs/content/docs/integrations/nest.mdx'
- 'letstri/permix:docs/content/docs/integrations/trpc.mdx'
- 'letstri/permix:docs/content/docs/integrations/orpc.mdx'
- 'letstri/permix:docs/content/docs/integrations/node.mdx'
diff --git a/_artifacts/skill_tree.yaml b/_artifacts/skill_tree.yaml
index 70b42866..011bfb17 100644
--- a/_artifacts/skill_tree.yaml
+++ b/_artifacts/skill_tree.yaml
@@ -40,7 +40,7 @@ skills:
isReady/isReadyAsync; PermixProvider/usePermix/createComponents and SSR
dehydrate/hydrate for React, Vue, Solid, Svelte, Next.js, TanStack
Start; setupMiddleware/checkMiddleware for Express, Hono, Fastify,
- tRPC, oRPC, Node, Elysia. Thin router with references loaded on demand.
+ NestJS, tRPC, oRPC, Node, Elysia. Thin router with references loaded on demand.
requires:
- permix-getting-started
subsystems:
@@ -53,6 +53,7 @@ skills:
- express
- hono
- fastify
+ - nest
- trpc
- orpc
- node
@@ -75,6 +76,7 @@ skills:
- 'letstri/permix:docs/content/docs/integrations/express.mdx'
- 'letstri/permix:docs/content/docs/integrations/hono.mdx'
- 'letstri/permix:docs/content/docs/integrations/fastify.mdx'
+ - 'letstri/permix:docs/content/docs/integrations/nest.mdx'
- 'letstri/permix:docs/content/docs/integrations/trpc.mdx'
- 'letstri/permix:docs/content/docs/integrations/orpc.mdx'
- 'letstri/permix:docs/content/docs/integrations/node.mdx'
diff --git a/docs/content/docs/comparison.mdx b/docs/content/docs/comparison.mdx
index d0d9ea67..64899ff7 100644
--- a/docs/content/docs/comparison.mdx
+++ b/docs/content/docs/comparison.mdx
@@ -46,6 +46,7 @@ Built with `pnpm run build` in the `permix` package (`tsdown` for all entries ex
| `permix/hono` | 0.88 kB | adapter |
| `permix/fastify` | 1.04 kB | adapter |
| `permix/elysia` | 0.88 kB | adapter |
+| `permix/nest` | 1.39 kB | adapter |
| `permix/trpc` | 0.96 kB | adapter |
| `permix/orpc` | 0.92 kB | adapter |
| `permix/effect` | 1.28 kB | adapter |
diff --git a/docs/content/docs/integrations/nest.mdx b/docs/content/docs/integrations/nest.mdx
new file mode 100644
index 00000000..4a1ebe3a
--- /dev/null
+++ b/docs/content/docs/integrations/nest.mdx
@@ -0,0 +1,261 @@
+---
+title: NestJS
+description: Learn how to use Permix with NestJS
+---
+
+## Overview
+
+Permix provides a NestJS integration that sets up permissions per request and enforces them with a guard plus a `@Check` decorator. The factory is created using `createPermix` from `permix/nest`.
+
+
+ Before getting started with the NestJS integration, make sure you've completed
+ the initial setup steps in the [Quick Start](/docs/quick-start) guide.
+
+
+
+
+
+
+## Setup
+
+Create a Permix factory and register its guard as a global `APP_GUARD`. The guard always attaches a per-request instance; it only enforces a permission when `@Check` is present.
+
+```ts
+import { Module } from '@nestjs/common'
+import { APP_GUARD } from '@nestjs/core'
+import { createPermix } from 'permix/nest'
+
+interface Post {
+ id: string
+ authorId: string
+ title: string
+ content: string
+}
+
+export const permix = createPermix<{
+ post: [
+ { name: 'create'; type: Post },
+ { name: 'read'; type: Post },
+ { name: 'update'; type: Post },
+ ]
+}>()
+
+@Module({
+ providers: [
+ {
+ provide: APP_GUARD,
+ useValue: permix.guard(({ req }) => {
+ // You can access req.user or other properties to determine permissions
+ return {
+ post: {
+ create: true,
+ read: true,
+ update: false,
+ },
+ }
+ }),
+ },
+ ],
+})
+export class AppModule {}
+```
+
+
+ The guard works with both the Express and Fastify Nest HTTP adapters. It
+ preserves full type safety from your Permix definition.
+
+
+
+
+
+
+## Checking Permissions
+
+Use the `Check` decorator on a handler or controller:
+
+```ts
+import { Controller, Delete, Get, Post, Put } from '@nestjs/common'
+import { permix } from './permix'
+
+@Controller('posts')
+export class PostsController {
+ @Post()
+ @permix.Check('post.create')
+ create() {
+ return { success: true }
+ }
+
+ @Put(':id')
+ @permix.Check((c) => c('post.read') && c('post.update'))
+ update() {
+ return { success: true }
+ }
+
+ @Delete(':id')
+ @permix.Check('post.~all')
+ remove() {
+ return { success: true }
+ }
+
+ @Get()
+ @permix.Check('post.~any')
+ findAll() {
+ return { posts: getAllPosts() }
+ }
+}
+```
+
+
+
+
+
+## Accessing Permix Directly
+
+You can access the Permix instance directly in your route handlers using the `get` function:
+
+```ts
+@Get()
+findAll(@Req() req: Request) {
+ const { check } = permix.getOrThrow(req)
+
+ if (check('post.read')) {
+ return { posts: getAllPosts() }
+ }
+
+ throw new ForbiddenException({
+ error: 'You do not have permission to read posts',
+ })
+}
+```
+
+Entity-based (ReBAC) checks usually run in the handler after the resource is loaded:
+
+```ts
+@Put(':id')
+async update(@Param('id') id: string, @Req() req: Request) {
+ const post = await getPostById(id)
+ const { check } = permix.getOrThrow(req)
+
+ if (!check('post.update', post)) {
+ throw new ForbiddenException({ error: 'You cannot update this post' })
+ }
+
+ return { success: true }
+}
+```
+
+
+
+
+
+## Using Templates
+
+Permix provides a template helper to create reusable permission rule sets:
+
+```ts
+const adminTemplate = permix.template({
+ post: {
+ create: true,
+ read: true,
+ update: true,
+ },
+})
+
+{
+ provide: APP_GUARD,
+ useValue: permix.guard(({ req }) => {
+ if (req.user?.role === 'admin') {
+ return adminTemplate()
+ }
+
+ return {
+ post: {
+ create: false,
+ read: true,
+ update: false,
+ },
+ }
+ }),
+}
+```
+
+
+
+
+
+## Custom Error Handling
+
+By default, a denied `@Check` throws a Nest `ForbiddenException` with `{ error: 'Forbidden' }`. You can customize this by providing an `onForbidden` handler:
+
+### Basic Error Handler
+
+```ts
+const permix = createPermix({
+ onForbidden: () => {
+ throw new ForbiddenException({
+ error: 'Custom forbidden message',
+ })
+ },
+})
+```
+
+### Dynamic Error Handler
+
+You can also throw different responses based on the checked path:
+
+```ts
+const permix = createPermix({
+ onForbidden: ({ path }) => {
+ if (path === 'post.create') {
+ throw new ForbiddenException({
+ error: `You don't have permission for ${path}`,
+ })
+ }
+
+ throw new ForbiddenException({
+ error: 'You do not have permission to perform this action',
+ })
+ },
+})
+```
+
+The `onForbidden` handler receives:
+
+- `req`: The HTTP request object (Express or Fastify)
+- `context`: Nest `ExecutionContext`
+- `path`: The permission path that was checked (or `null` for callback checks)
+- `data`: Optional entity data passed to the check
+
+## Advanced Usage
+
+### Async Permission Rules
+
+You can use async functions in your guard setup:
+
+```ts
+permix.guard(async ({ req }) => {
+ const userPermissions = await getUserPermissions(req.user.id)
+
+ return {
+ post: {
+ create: userPermissions.canCreatePosts,
+ read: userPermissions.canReadPosts,
+ update: userPermissions.canUpdatePosts,
+ },
+ }
+})
+```
+
+### Hooks
+
+You can register hooks at the factory level to listen for events across all requests:
+
+```ts
+permix.hook('check', ({ path, data }) => {
+ console.log(`Permission checked: ${path}`, data)
+})
+```
+
+### Example
+
+You can find the example of the NestJS integration [here](https://github.com/letstri/permix/tree/main/examples/nest).
diff --git a/docs/content/docs/meta.json b/docs/content/docs/meta.json
index dbd79bfc..36552fe8 100644
--- a/docs/content/docs/meta.json
+++ b/docs/content/docs/meta.json
@@ -29,6 +29,7 @@
"integrations/hono",
"integrations/elysia",
"integrations/fastify",
+ "integrations/nest",
"integrations/effect",
"integrations/drizzle",
"---",
diff --git a/examples/nest/main.ts b/examples/nest/main.ts
new file mode 100644
index 00000000..291b4b7a
--- /dev/null
+++ b/examples/nest/main.ts
@@ -0,0 +1,67 @@
+import 'reflect-metadata'
+import {
+ Controller,
+ ForbiddenException,
+ Get,
+ Module,
+ Req,
+} from '@nestjs/common'
+import { APP_GUARD, NestFactory } from '@nestjs/core'
+import type { ValidateDefinition } from 'permix'
+import { createPermix } from 'permix/nest'
+
+type PermissionsDefinition = ValidateDefinition<{
+ user: ['read', 'write']
+}>
+
+const permix = createPermix({
+ onForbidden: () => {
+ throw new ForbiddenException({
+ error: 'You do not have permission to access this resource',
+ })
+ },
+})
+
+@Controller()
+class AppController {
+ @Get()
+ @permix.Check('user.read')
+ read() {
+ return 'Hello World'
+ }
+
+ @Get('write')
+ @permix.Check('user.write')
+ write() {
+ return 'Hello World'
+ }
+
+ @Get('permix')
+ inspect(@Req() req: { [key: PropertyKey]: unknown }) {
+ return { canRead: permix.getOrThrow(req).check('user.read') }
+ }
+}
+
+@Module({
+ controllers: [AppController],
+ providers: [
+ {
+ provide: APP_GUARD,
+ useValue: permix.guard(() => ({
+ user: {
+ read: true,
+ write: false,
+ },
+ })),
+ },
+ ],
+})
+class AppModule {}
+
+async function bootstrap() {
+ const app = await NestFactory.create(AppModule)
+ await app.listen(3000)
+ console.log('Server is running on port 3000')
+}
+
+bootstrap()
diff --git a/examples/nest/package.json b/examples/nest/package.json
new file mode 100644
index 00000000..425acf92
--- /dev/null
+++ b/examples/nest/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "nest",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "check-types": "tsc --noEmit",
+ "start": "tsx main.ts"
+ },
+ "dependencies": {
+ "@nestjs/common": "^11.1.6",
+ "@nestjs/core": "^11.1.6",
+ "@nestjs/platform-express": "^11.1.6",
+ "permix": "workspace:*",
+ "reflect-metadata": "^0.2.2",
+ "rxjs": "^7.8.2"
+ },
+ "devDependencies": {
+ "tsx": "^4.22.4",
+ "typescript": "^6.0.3"
+ }
+}
diff --git a/examples/nest/tsconfig.json b/examples/nest/tsconfig.json
new file mode 100644
index 00000000..c123cb28
--- /dev/null
+++ b/examples/nest/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true
+ }
+}
diff --git a/ignores.ts b/ignores.ts
index 9e77439e..65349182 100644
--- a/ignores.ts
+++ b/ignores.ts
@@ -3,6 +3,8 @@ export const ignorePatterns = [
'**/.next/**',
'**/.turbo/**',
'**/.vercel/**',
+ '**/.agents/**',
+ '**/.claude/**',
'**/dist/**',
'**/build/**',
'**/coverage/**',
diff --git a/oxlint.config.ts b/oxlint.config.ts
index e29a4dc3..5b3df5ee 100644
--- a/oxlint.config.ts
+++ b/oxlint.config.ts
@@ -125,5 +125,12 @@ export default defineConfig({
'react/jsx-key': 'off',
},
},
+ {
+ files: ['permix/src/nest/**', 'examples/nest/**'],
+ rules: {
+ 'class-methods-use-this': 'off',
+ 'typescript/no-extraneous-class': 'off',
+ },
+ },
],
})
diff --git a/permix/package.json b/permix/package.json
index dfbefd33..f2f1958f 100644
--- a/permix/package.json
+++ b/permix/package.json
@@ -10,6 +10,7 @@
"authorization",
"frontend",
"javascript",
+ "nestjs",
"nextjs",
"permissions",
"permissions-management",
@@ -116,6 +117,10 @@
"./tanstack-start": {
"types": "./dist/tanstack-start/index.d.mts",
"import": "./dist/tanstack-start/index.mjs"
+ },
+ "./nest": {
+ "types": "./dist/nest/index.d.mts",
+ "import": "./dist/nest/index.mjs"
}
},
"scripts": {
@@ -129,6 +134,10 @@
"test": "vitest run"
},
"devDependencies": {
+ "@nestjs/common": "^11.2.3",
+ "@nestjs/core": "^11.2.3",
+ "@nestjs/platform-express": "^11.2.3",
+ "@nestjs/testing": "^11.2.3",
"@solidjs/testing-library": "^0.8.10",
"@sveltejs/package": "^2.5.7",
"@sveltejs/vite-plugin-svelte": "^7.1.2",
@@ -148,6 +157,8 @@
"effect": "^3.21.2",
"happy-dom": "^20.9.0",
"react-dom": "^19.2.6",
+ "reflect-metadata": "^0.2.2",
+ "rxjs": "^7.8.2",
"supertest": "^7.2.2",
"svelte": "^5.56.0",
"svelte-check": "^4.5.0",
@@ -159,6 +170,8 @@
"zod": "^4.4.3"
},
"peerDependencies": {
+ "@nestjs/common": ">=10",
+ "@nestjs/core": ">=10",
"@orpc/server": ">=1",
"@tanstack/react-start": ">=1",
"@trpc/server": ">=11",
@@ -177,6 +190,12 @@
"vue": ">=3"
},
"peerDependenciesMeta": {
+ "@nestjs/common": {
+ "optional": true
+ },
+ "@nestjs/core": {
+ "optional": true
+ },
"@orpc/server": {
"optional": true
},
diff --git a/permix/skills/README.md b/permix/skills/README.md
index f2616977..b78dbd84 100644
--- a/permix/skills/README.md
+++ b/permix/skills/README.md
@@ -42,7 +42,7 @@ Restart Cursor or start a new agent chat so skills are picked up.
| Skill | Intent id | When to use |
| --- | --- | --- |
| [permix-getting-started](./permix-getting-started/SKILL.md) | `permix#permix-getting-started` | New project, schema, `setup`, roles/templates |
-| [permix](./permix/SKILL.md) | `permix#permix` | Everything past setup: `check`/ReBAC (`references/check.md`), React/Vue/Solid/Svelte + SSR (`references/frontend.md`), Express/Hono/Fastify/tRPC/oRPC middleware (`references/server.md`) |
+| [permix](./permix/SKILL.md) | `permix#permix` | Everything past setup: `check`/ReBAC (`references/check.md`), React/Vue/Solid/Svelte + SSR (`references/frontend.md`), Express/Hono/Fastify/NestJS/tRPC/oRPC middleware (`references/server.md`) |
## Registry and version history
diff --git a/permix/skills/permix/SKILL.md b/permix/skills/permix/SKILL.md
index b3ebc1b5..45f2b62a 100644
--- a/permix/skills/permix/SKILL.md
+++ b/permix/skills/permix/SKILL.md
@@ -1,7 +1,7 @@
---
name: permix
description: >-
- Applies Permix authorization once a schema exists: permix.check() paths and ReBAC callbacks, frontend bindings (permix/react, permix/vue, permix/solid, permix/svelte) with SSR dehydrate/hydrate for Next.js and TanStack Start, and server middleware (permix/express, hono, fastify, trpc, orpc, node, elysia). Use for anything past initial setup — checking permissions, gating UI, or protecting routes. For creating the schema and first `permix.setup()`, use permix-getting-started first.
+ Applies Permix authorization once a schema exists: permix.check() paths and ReBAC callbacks, frontend bindings (permix/react, permix/vue, permix/solid, permix/svelte) with SSR dehydrate/hydrate for Next.js and TanStack Start, and server middleware (permix/express, hono, fastify, nest, trpc, orpc, node, elysia). Use for anything past initial setup — checking permissions, gating UI, or protecting routes. For creating the schema and first `permix.setup()`, use permix-getting-started first.
metadata:
type: core
library: permix
@@ -22,6 +22,7 @@ sources:
- 'letstri/permix:docs/content/docs/integrations/express.mdx'
- 'letstri/permix:docs/content/docs/integrations/hono.mdx'
- 'letstri/permix:docs/content/docs/integrations/fastify.mdx'
+ - 'letstri/permix:docs/content/docs/integrations/nest.mdx'
- 'letstri/permix:docs/content/docs/integrations/trpc.mdx'
- 'letstri/permix:docs/content/docs/integrations/orpc.mdx'
- 'letstri/permix:docs/content/docs/integrations/node.mdx'
@@ -38,7 +39,7 @@ Assumes a `permix` instance already exists (see **permix-getting-started**). Loa
| --- | --- |
| `permix.check()` paths, callbacks, `~all`/`~any`, ReBAC/ABAC with entity data, `isReady` | [references/check.md](references/check.md) |
| React, Vue, Solid, or Svelte UI — `PermixProvider`, `usePermix`, `createComponents`, SSR `dehydrate`/`hydrate` for Next.js / TanStack Start | [references/frontend.md](references/frontend.md) |
-| Protecting Express, Hono, Fastify, tRPC, oRPC, Node, or Elysia routes — `setupMiddleware`, `checkMiddleware` | [references/server.md](references/server.md) |
+| Protecting Express, Hono, Fastify, NestJS, tRPC, oRPC, Node, or Elysia routes — `setupMiddleware`, `checkMiddleware`, or Nest `guard` / `@Check` | [references/server.md](references/server.md) |
## Rules that apply everywhere
diff --git a/permix/skills/permix/references/server.md b/permix/skills/permix/references/server.md
index 8b8c79d5..0306544f 100644
--- a/permix/skills/permix/references/server.md
+++ b/permix/skills/permix/references/server.md
@@ -4,7 +4,7 @@ Authorization must run on the server. Client checks are UX only.
Docs: https://permix.letstri.dev/docs/integrations/express
-## Pattern (Express-style; similar for Hono, Fastify, Node)
+## Pattern (Express-style; similar for Hono, Fastify, Node, Nest)
Import from the framework subpath, not bare `permix`:
@@ -59,6 +59,32 @@ app.delete(
Denied requests default to `403` with `{ error: 'Forbidden' }`. Customize with `onForbidden` in `createPermix` options.
+### NestJS (`permix/nest`)
+
+Use a global `APP_GUARD` plus `@Check`. The guard always sets up the per-request instance and only enforces a path when the decorator is present:
+
+```ts
+import { APP_GUARD } from '@nestjs/core'
+import { createPermix } from 'permix/nest'
+
+const permix = createPermix<{
+ post: ['create', 'read']
+}>()
+
+{
+ provide: APP_GUARD,
+ useValue: permix.guard(({ req }) => ({
+ post: { create: !!req.user, read: true },
+ })),
+}
+
+@Get()
+@permix.Check('post.read')
+findAll() {}
+```
+
+Entity checks run in the handler after the resource is loaded: `permix.getOrThrow(req).check('post.update', post)`.
+
### Access instance in handlers
```ts
@@ -77,6 +103,7 @@ app.get('/posts/:id', (req, res) => {
| Express | `permix/express` |
| Hono | `permix/hono` |
| Fastify | `permix/fastify` |
+| NestJS | `permix/nest` |
| tRPC | `permix/trpc` |
| oRPC | `permix/orpc` |
| Generic HTTP | `permix/node` or `permix/server` |
@@ -101,7 +128,7 @@ app.use(permix.setupMiddleware(rules))
## Checklist
-- [ ] `setupMiddleware` runs **before** `checkMiddleware` on protected routes
+- [ ] `setupMiddleware` runs **before** `checkMiddleware` on protected routes (Nest: register `permix.guard(...)` as `APP_GUARD` before `@Check`)
- [ ] Rules derived from authenticated `req.user` (or RPC context), not client headers alone
- [ ] Entity checks pass resource data when the action has `type` / `required: true`
- [ ] Same paths as frontend (`post.update`, not ad-hoc strings)
diff --git a/permix/src/nest/index.ts b/permix/src/nest/index.ts
new file mode 100644
index 00000000..60dafabb
--- /dev/null
+++ b/permix/src/nest/index.ts
@@ -0,0 +1 @@
+export * from './permix'
diff --git a/permix/src/nest/permix.test.ts b/permix/src/nest/permix.test.ts
new file mode 100644
index 00000000..27fa3992
--- /dev/null
+++ b/permix/src/nest/permix.test.ts
@@ -0,0 +1,643 @@
+import 'reflect-metadata'
+import type { INestApplication, Type } from '@nestjs/common'
+import {
+ Controller,
+ ForbiddenException,
+ Get,
+ Module,
+ Post,
+ Req,
+} from '@nestjs/common'
+import { APP_GUARD } from '@nestjs/core'
+import { Test } from '@nestjs/testing'
+import request from 'supertest'
+import { afterEach, describe, expect, it } from 'vitest'
+
+import type { ValidateDefinition } from '../core'
+import { createPermix } from './permix'
+
+interface PostEntity {
+ id: string
+ authorId: string
+}
+
+type PermissionsDefinition = ValidateDefinition<{
+ post: ['create', 'read', 'update']
+ user: ['delete']
+}>
+
+type PostWithData = ValidateDefinition<{
+ post: [{ name: 'create'; type: PostEntity }]
+}>
+
+const denied = {
+ post: { create: false, read: false, update: false },
+ user: { delete: false },
+}
+
+describe('permix/nest', () => {
+ let app: INestApplication | undefined
+
+ afterEach(async () => {
+ if (app) {
+ await app.close()
+ app = undefined
+ }
+ })
+
+ async function createApp(module: Type): Promise {
+ const moduleRef = await Test.createTestingModule({
+ imports: [module],
+ }).compile()
+ app = moduleRef.createNestApplication({ logger: false })
+ await app.init()
+ return app
+ }
+
+ describe(createPermix, () => {
+ const permix = createPermix()
+
+ it('should throw ts error', () => {
+ // @ts-expect-error path does not exist
+ permix.Check('post.delete')
+ })
+
+ it('should allow access when permission is granted', async () => {
+ @Controller()
+ class PostsController {
+ @Post('posts')
+ @permix.Check('post.create')
+ create() {
+ return { success: true }
+ }
+ }
+
+ @Module({
+ controllers: [PostsController],
+ providers: [
+ {
+ provide: APP_GUARD,
+ useValue: permix.guard({
+ post: { create: true, read: false, update: false },
+ user: { delete: false },
+ }),
+ },
+ ],
+ })
+ class AppModule {}
+
+ const nestApp = await createApp(AppModule)
+ const response = await request(nestApp.getHttpServer())
+ .post('/posts')
+ .send({ title: 'Test Post' })
+
+ expect(response.status).toBe(201)
+ expect(response.body).toStrictEqual({ success: true })
+ })
+
+ it('should deny access when permission is not granted', async () => {
+ @Controller()
+ class PostsController {
+ @Post('posts')
+ @permix.Check('post.create')
+ create() {
+ return { success: true }
+ }
+ }
+
+ @Module({
+ controllers: [PostsController],
+ providers: [{ provide: APP_GUARD, useValue: permix.guard(denied) }],
+ })
+ class AppModule {}
+
+ const nestApp = await createApp(AppModule)
+ const response = await request(nestApp.getHttpServer())
+ .post('/posts')
+ .send({ title: 'Test Post' })
+
+ expect(response.status).toBe(403)
+ expect(response.body).toStrictEqual({ error: 'Forbidden' })
+ })
+
+ it('should work with custom error handler', async () => {
+ const permix = createPermix({
+ onForbidden: () => {
+ throw new ForbiddenException({ error: 'Custom error' })
+ },
+ })
+
+ @Controller()
+ class PostsController {
+ @Post('posts')
+ @permix.Check('post.create')
+ create() {
+ return { success: true }
+ }
+ }
+
+ @Module({
+ controllers: [PostsController],
+ providers: [{ provide: APP_GUARD, useValue: permix.guard(denied) }],
+ })
+ class AppModule {}
+
+ const nestApp = await createApp(AppModule)
+ const response = await request(nestApp.getHttpServer())
+ .post('/posts')
+ .send({ title: 'Test Post' })
+
+ expect(response.status).toBe(403)
+ expect(response.body).toStrictEqual({ error: 'Custom error' })
+ })
+
+ it('should work with custom error and params', async () => {
+ const permix = createPermix({
+ onForbidden: ({ path }) => {
+ throw new ForbiddenException({
+ error: `You do not have permission for ${path}`,
+ })
+ },
+ })
+
+ @Controller()
+ class PostsController {
+ @Post('posts')
+ @permix.Check('post.create')
+ create() {
+ return { success: true }
+ }
+ }
+
+ @Module({
+ controllers: [PostsController],
+ providers: [{ provide: APP_GUARD, useValue: permix.guard(denied) }],
+ })
+ class AppModule {}
+
+ const nestApp = await createApp(AppModule)
+ const response = await request(nestApp.getHttpServer())
+ .post('/posts')
+ .send({ title: 'Test Post' })
+
+ expect(response.status).toBe(403)
+ expect(response.body).toStrictEqual({
+ error: 'You do not have permission for post.create',
+ })
+ })
+
+ it('should pass data through to a rule callback', async () => {
+ const permix = createPermix()
+
+ @Controller()
+ class PostsController {
+ @Post('posts')
+ @permix.Check('post.create', { id: 'a', authorId: '1' })
+ create() {
+ return { success: true }
+ }
+ }
+
+ @Module({
+ controllers: [PostsController],
+ providers: [
+ {
+ provide: APP_GUARD,
+ useValue: permix.guard({
+ post: {
+ create: (post) => post?.authorId === '1',
+ },
+ }),
+ },
+ ],
+ })
+ class AppModule {}
+
+ const nestApp = await createApp(AppModule)
+ const response = await request(nestApp.getHttpServer())
+ .post('/posts')
+ .send({ title: 'Test Post' })
+
+ expect(response.status).toBe(201)
+ expect(response.body).toStrictEqual({ success: true })
+ })
+
+ it('should work with checker callback form', async () => {
+ const permix = createPermix()
+
+ @Controller()
+ class PostsController {
+ @Post('posts')
+ @permix.Check((c) => c('post.create') && c('user.delete'))
+ create() {
+ return { success: true }
+ }
+ }
+
+ @Module({
+ controllers: [PostsController],
+ providers: [
+ {
+ provide: APP_GUARD,
+ useValue: permix.guard({
+ post: { create: true, read: true, update: false },
+ user: { delete: true },
+ }),
+ },
+ ],
+ })
+ class AppModule {}
+
+ const nestApp = await createApp(AppModule)
+ const response = await request(nestApp.getHttpServer())
+ .post('/posts')
+ .send({ title: 'Test Post' })
+
+ expect(response.status).toBe(201)
+ expect(response.body).toStrictEqual({ success: true })
+ })
+
+ it('should work with template', async () => {
+ const template = permix.template({
+ post: { create: true, read: true, update: true },
+ user: { delete: true },
+ })
+
+ @Controller()
+ class PostsController {
+ @Post('posts')
+ @permix.Check('post.create')
+ create() {
+ return { success: true }
+ }
+ }
+
+ @Module({
+ controllers: [PostsController],
+ providers: [
+ { provide: APP_GUARD, useValue: permix.guard(() => template()) },
+ ],
+ })
+ class AppModule {}
+
+ const nestApp = await createApp(AppModule)
+ const response = await request(nestApp.getHttpServer())
+ .post('/posts')
+ .send({ title: 'Test Post' })
+
+ expect(response.status).toBe(201)
+ expect(response.body).toStrictEqual({ success: true })
+ })
+
+ it('should dehydrate permissions', async () => {
+ const template = permix.template({
+ post: { create: true, read: false, update: true },
+ user: { delete: false },
+ })
+
+ @Controller()
+ class DehydrateController {
+ @Get('dehydrate')
+ dehydrate(@Req() req: { [key: PropertyKey]: unknown }) {
+ return permix.getOrThrow(req).dehydrate()
+ }
+ }
+
+ @Module({
+ controllers: [DehydrateController],
+ providers: [
+ { provide: APP_GUARD, useValue: permix.guard(() => template()) },
+ ],
+ })
+ class AppModule {}
+
+ const nestApp = await createApp(AppModule)
+ const response = await request(nestApp.getHttpServer()).get('/dehydrate')
+
+ expect(response.status).toBe(200)
+ expect(response.body).toStrictEqual({
+ post: { create: true, read: false, update: true },
+ user: { delete: false },
+ })
+ })
+
+ it('should allow a handler without Check after setup', async () => {
+ @Controller()
+ class OpenController {
+ @Get('open')
+ open() {
+ return { ok: true }
+ }
+ }
+
+ @Module({
+ controllers: [OpenController],
+ providers: [{ provide: APP_GUARD, useValue: permix.guard(denied) }],
+ })
+ class AppModule {}
+
+ const nestApp = await createApp(AppModule)
+ const response = await request(nestApp.getHttpServer()).get('/open')
+ expect(response.status).toBe(200)
+ expect(response.body).toStrictEqual({ ok: true })
+ })
+
+ it('should let two factories with different keys coexist on the same request', async () => {
+ const admin = createPermix().contextKey('admin')
+ const guest = createPermix().contextKey('guest')
+
+ @Controller()
+ class DualController {
+ @Post('admin')
+ @admin.Check('post.create')
+ adminRoute() {
+ return { scope: 'admin' }
+ }
+
+ @Post('guest')
+ @guest.Check('post.create')
+ guestRoute() {
+ return { scope: 'guest' }
+ }
+ }
+
+ @Module({
+ controllers: [DualController],
+ providers: [
+ {
+ provide: APP_GUARD,
+ useValue: admin.guard({
+ post: { create: true, read: true, update: true },
+ user: { delete: true },
+ }),
+ },
+ {
+ provide: APP_GUARD,
+ useValue: guest.guard({
+ post: { create: false, read: true, update: false },
+ user: { delete: false },
+ }),
+ },
+ ],
+ })
+ class AppModule {}
+
+ const nestApp = await createApp(AppModule)
+
+ const adminResponse = await request(nestApp.getHttpServer()).post(
+ '/admin'
+ )
+ expect(adminResponse.status).toBe(201)
+ expect(adminResponse.body).toStrictEqual({ scope: 'admin' })
+
+ const guestResponse = await request(nestApp.getHttpServer()).post(
+ '/guest'
+ )
+ expect(guestResponse.status).toBe(403)
+ expect(guestResponse.body).toStrictEqual({ error: 'Forbidden' })
+ })
+
+ it('should default to a per-instance symbol so two factories without a key do not collide', async () => {
+ const first = createPermix()
+ const second = createPermix()
+
+ @Controller()
+ class DualController {
+ @Post('first')
+ @first.Check('post.create')
+ firstRoute() {
+ return { ok: true }
+ }
+
+ @Post('second')
+ @second.Check('post.create')
+ secondRoute() {
+ return { ok: true }
+ }
+ }
+
+ @Module({
+ controllers: [DualController],
+ providers: [
+ {
+ provide: APP_GUARD,
+ useValue: first.guard({
+ post: { create: true, read: true, update: true },
+ user: { delete: true },
+ }),
+ },
+ {
+ provide: APP_GUARD,
+ useValue: second.guard({
+ post: { create: false, read: false, update: false },
+ user: { delete: false },
+ }),
+ },
+ ],
+ })
+ class AppModule {}
+
+ const nestApp = await createApp(AppModule)
+
+ const firstResponse = await request(nestApp.getHttpServer()).post(
+ '/first'
+ )
+ expect(firstResponse.status).toBe(201)
+
+ const secondResponse = await request(nestApp.getHttpServer()).post(
+ '/second'
+ )
+ expect(secondResponse.status).toBe(403)
+ })
+
+ it('should accept an explicit symbol key', async () => {
+ const key = Symbol('my-permix')
+ const permix = createPermix().contextKey(key)
+
+ @Controller()
+ class ProbeController {
+ @Get('probe')
+ probe(@Req() req: { [key: PropertyKey]: unknown }) {
+ return { attached: Boolean(req[key]) }
+ }
+ }
+
+ @Module({
+ controllers: [ProbeController],
+ providers: [
+ {
+ provide: APP_GUARD,
+ useValue: permix.guard({
+ post: { create: true, read: true, update: true },
+ user: { delete: true },
+ }),
+ },
+ ],
+ })
+ class AppModule {}
+
+ const nestApp = await createApp(AppModule)
+ const response = await request(nestApp.getHttpServer()).get('/probe')
+ expect(response.status).toBe(200)
+ expect(response.body).toStrictEqual({ attached: true })
+ })
+
+ it('should honor a class-level Check decorator', async () => {
+ @Controller('posts')
+ @permix.Check('post.create')
+ class PostsController {
+ @Post()
+ create() {
+ return { success: true }
+ }
+ }
+
+ @Module({
+ controllers: [PostsController],
+ providers: [{ provide: APP_GUARD, useValue: permix.guard(denied) }],
+ })
+ class AppModule {}
+
+ const nestApp = await createApp(AppModule)
+ const response = await request(nestApp.getHttpServer()).post('/posts')
+ expect(response.status).toBe(403)
+ expect(response.body).toStrictEqual({ error: 'Forbidden' })
+ })
+ })
+
+ describe('get / getOrThrow', () => {
+ const permix = createPermix()
+
+ it('should return null when the guard has not run', async () => {
+ @Controller()
+ class RootController {
+ @Get()
+ root(@Req() req: { [key: PropertyKey]: unknown }) {
+ return { result: permix.get(req) }
+ }
+ }
+
+ @Module({ controllers: [RootController] })
+ class AppModule {}
+
+ const nestApp = await createApp(AppModule)
+ const response = await request(nestApp.getHttpServer()).get('/')
+ expect(response.status).toBe(200)
+ expect(response.body).toStrictEqual({ result: null })
+ })
+
+ it('should return the instance when the guard has run', async () => {
+ @Controller()
+ class RootController {
+ @Get()
+ root(@Req() req: { [key: PropertyKey]: unknown }) {
+ const p = permix.getOrThrow(req)
+ return { hasCheck: typeof p.check === 'function' }
+ }
+ }
+
+ @Module({
+ controllers: [RootController],
+ providers: [
+ {
+ provide: APP_GUARD,
+ useValue: permix.guard({
+ post: { create: true, read: true, update: true },
+ user: { delete: true },
+ }),
+ },
+ ],
+ })
+ class AppModule {}
+
+ const nestApp = await createApp(AppModule)
+ const response = await request(nestApp.getHttpServer()).get('/')
+ expect(response.status).toBe(200)
+ expect(response.body).toStrictEqual({ hasCheck: true })
+ })
+
+ it('getOrThrow should throw PermixNotFoundError when missing', async () => {
+ @Controller()
+ class RootController {
+ @Get()
+ root(@Req() req: { [key: PropertyKey]: unknown }) {
+ permix.getOrThrow(req)
+ return { ok: true }
+ }
+ }
+
+ @Module({ controllers: [RootController] })
+ class AppModule {}
+
+ const nestApp = await createApp(AppModule)
+ const response = await request(nestApp.getHttpServer()).get('/')
+ expect(response.status).toBe(500)
+ expect(response.body.statusCode).toBe(500)
+ })
+
+ it('getRules should return null when the guard has not run', async () => {
+ @Controller()
+ class RootController {
+ @Get()
+ root(@Req() req: { [key: PropertyKey]: unknown }) {
+ return { rules: permix.getRules(req) }
+ }
+ }
+
+ @Module({ controllers: [RootController] })
+ class AppModule {}
+
+ const nestApp = await createApp(AppModule)
+ const response = await request(nestApp.getHttpServer()).get('/')
+ expect(response.status).toBe(200)
+ expect(response.body).toStrictEqual({ rules: null })
+ })
+
+ it('getRules should return the current rules when the guard has run', async () => {
+ @Controller()
+ class RootController {
+ @Get()
+ root(@Req() req: { [key: PropertyKey]: unknown }) {
+ return { rules: permix.getRules(req) }
+ }
+ }
+
+ @Module({
+ controllers: [RootController],
+ providers: [
+ {
+ provide: APP_GUARD,
+ useValue: permix.guard({
+ post: { create: true, read: false, update: false },
+ user: { delete: true },
+ }),
+ },
+ ],
+ })
+ class AppModule {}
+
+ const nestApp = await createApp(AppModule)
+ const response = await request(nestApp.getHttpServer()).get('/')
+ expect(response.status).toBe(200)
+ expect(response.body).toStrictEqual({
+ rules: {
+ post: { create: true, read: false, update: false },
+ user: { delete: true },
+ },
+ })
+ })
+ })
+
+ describe('key exposure', () => {
+ it('should expose the key on the factory return', () => {
+ const permix =
+ createPermix().contextKey('custom-key')
+ expect(permix.key).toBe('custom-key')
+ })
+
+ it('should expose a symbol key when using default', () => {
+ const permix = createPermix()
+ expect(permix.key).toBeTypeOf('symbol')
+ })
+ })
+})
diff --git a/permix/src/nest/permix.ts b/permix/src/nest/permix.ts
new file mode 100644
index 00000000..7d1f8bc1
--- /dev/null
+++ b/permix/src/nest/permix.ts
@@ -0,0 +1,214 @@
+import type {
+ CanActivate,
+ CustomDecorator,
+ ExecutionContext,
+} from '@nestjs/common'
+import { ForbiddenException, SetMetadata } from '@nestjs/common'
+
+import type { Permix as PermixCore } from '../core'
+import {
+ createCheckContext,
+ createHooks,
+ createPermix as createPermixCore,
+ createTemplate,
+ PermixNotFoundError,
+} from '../core'
+import type { CheckArgs, CheckContext } from '../core/check'
+import type { Definition } from '../core/definitions'
+import type { PermixHooks, Rules, RulesPaths } from '../core/permix'
+import type { MaybePromise } from '../utils'
+
+/**
+ * HTTP request object from `ExecutionContext.switchToHttp().getRequest()`.
+ * Compatible with both the Express and Fastify Nest adapters.
+ */
+export type NestHttpRequest = Record
+
+export interface GuardContext {
+ req: NestHttpRequest
+ context: ExecutionContext
+}
+
+export interface PermixOptions {
+ /**
+ * Called when a `@Check` decorator denies the request. Defaults to throwing
+ * a Nest `ForbiddenException` with `{ error: 'Forbidden' }`.
+ */
+ onForbidden?: (params: CheckContext & GuardContext) => MaybePromise
+}
+
+function getRequest(context: ExecutionContext): NestHttpRequest {
+ return context.switchToHttp().getRequest()
+}
+
+function readCheckArgs(
+ metadataKey: string | symbol,
+ context: ExecutionContext
+): CheckArgs | undefined {
+ const handler = context.getHandler()
+ const classRef = context.getClass()
+ const fromHandler = Reflect.getMetadata(metadataKey, handler) as
+ | CheckArgs
+ | undefined
+ if (fromHandler) {
+ return fromHandler
+ }
+ return Reflect.getMetadata(metadataKey, classRef) as CheckArgs | undefined
+}
+
+function buildPermix(
+ resolveKey: () => string | symbol,
+ options: PermixOptions = {}
+) {
+ const checkMetadataKey = Symbol('permix:check')
+ const onForbidden =
+ options.onForbidden ??
+ (() => {
+ throw new ForbiddenException({ error: 'Forbidden' })
+ })
+
+ const hooks = createHooks>()
+
+ function get(req: NestHttpRequest): PermixCore | null {
+ const instance = req[resolveKey()] as PermixCore | undefined
+ return instance ?? null
+ }
+
+ function getOrThrow(req: NestHttpRequest): PermixCore {
+ const instance = get(req)
+ if (!instance) {
+ throw new PermixNotFoundError(resolveKey())
+ }
+ return instance
+ }
+
+ function attach(req: NestHttpRequest, rules: Rules): PermixCore {
+ const instance = createPermixCore(rules)
+ instance.hook('check', (context) => {
+ hooks.callHook('check', context)
+ })
+ req[resolveKey()] = instance
+ return instance
+ }
+
+ /**
+ * Nest guard that always sets up a per-request Permix instance, then enforces
+ * `@Check(...)` when that decorator is present on the handler or controller.
+ *
+ * Register globally with `APP_GUARD`, or per-controller / per-route with
+ * `@UseGuards`.
+ */
+ function guard(
+ callbackOrRules:
+ | ((context: GuardContext) => MaybePromise>)
+ | Rules
+ ): CanActivate {
+ return {
+ async canActivate(context) {
+ const req = getRequest(context)
+ const rules =
+ typeof callbackOrRules === 'function'
+ ? await callbackOrRules({ req, context })
+ : callbackOrRules
+ const instance = attach(req, rules)
+
+ const args = readCheckArgs(checkMetadataKey, context)
+ if (!args) {
+ return true
+ }
+
+ const allowed = instance.check(...args)
+ if (allowed) {
+ return true
+ }
+
+ await onForbidden({
+ req,
+ context,
+ ...createCheckContext(...args),
+ })
+ return false
+ },
+ }
+ }
+
+ /**
+ * Method or class decorator that records the permission check for `guard()`.
+ */
+ const Check: (...args: CheckArgs) => CustomDecorator = (
+ ...args
+ ) => SetMetadata(checkMetadataKey, args)
+
+ function getRules(req: NestHttpRequest): Rules | null {
+ return get(req)?.getRules() ?? null
+ }
+
+ function template(rules: Rules | ((param: T) => Rules)) {
+ return createTemplate(rules)
+ }
+
+ return {
+ guard,
+ Check,
+ template,
+ get,
+ getOrThrow,
+ getRules,
+ hook: hooks.hook,
+ hookOnce: hooks.hookOnce,
+ get key() {
+ return resolveKey()
+ },
+ $inferDefinition: undefined as unknown as D,
+ $inferPath: undefined as unknown as RulesPaths,
+ }
+}
+
+/**
+ * Create a guard factory that wires Permix into NestJS routes.
+ *
+ * Use `.contextKey('name')` to set a custom request key (defaults to a unique
+ * `Symbol('permix')`).
+ *
+ * @example
+ * ```ts
+ * import { APP_GUARD } from '@nestjs/core'
+ * import { createPermix } from 'permix/nest'
+ *
+ * const permix = createPermix<{
+ * post: ['create', 'read']
+ * }>()
+ *
+ * @Get()
+ * @permix.Check('post.read')
+ * findAll() {}
+ *
+ * // app.module.ts
+ * {
+ * provide: APP_GUARD,
+ * useValue: permix.guard(({ req }) => ({
+ * post: { create: !!req.user, read: true },
+ * })),
+ * }
+ * ```
+ *
+ * @link https://permix.letstri.dev/docs/integrations/nest
+ */
+export function createPermix(
+ options: PermixOptions = {}
+) {
+ let key: string | symbol = Symbol('permix')
+ const permix = buildPermix(() => key, options)
+
+ return Object.assign(permix, {
+ contextKey(newKey: string | symbol) {
+ key = newKey
+ return permix
+ },
+ })
+}
+
+/** Return type of {@link createPermix}. */
+export type NestPermix = ReturnType<
+ typeof createPermix
+>
diff --git a/permix/tsconfig.base.json b/permix/tsconfig.base.json
index cf0229e7..d24217e3 100644
--- a/permix/tsconfig.base.json
+++ b/permix/tsconfig.base.json
@@ -7,7 +7,8 @@
"strict": true,
"noEmit": true,
"forceConsistentCasingInFileNames": true,
- "skipLibCheck": true
+ "skipLibCheck": true,
+ "experimentalDecorators": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "*.ts"],
"exclude": [
diff --git a/permix/tsdown.config.ts b/permix/tsdown.config.ts
index 00e9a0a3..1a2cdb10 100644
--- a/permix/tsdown.config.ts
+++ b/permix/tsdown.config.ts
@@ -22,6 +22,7 @@ export default defineConfig({
'./src/drizzle/legacy/index.ts',
'./src/next/index.ts',
'./src/tanstack-start/index.ts',
+ './src/nest/index.ts',
],
dts: {
build: true,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 761d130a..3b37f901 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -263,6 +263,34 @@ importers:
specifier: ^8.0.16
version: 8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)
+ examples/nest:
+ dependencies:
+ '@nestjs/common':
+ specifier: ^11.1.6
+ version: 11.2.3(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/core':
+ specifier: ^11.1.6
+ version: 11.2.3(@nestjs/common@11.2.3(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.2.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/platform-express':
+ specifier: ^11.1.6
+ version: 11.2.3(@nestjs/common@11.2.3(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.2.3)
+ permix:
+ specifier: workspace:*
+ version: link:../../permix
+ reflect-metadata:
+ specifier: ^0.2.2
+ version: 0.2.2
+ rxjs:
+ specifier: ^7.8.2
+ version: 7.8.2
+ devDependencies:
+ tsx:
+ specifier: ^4.22.4
+ version: 4.22.4
+ typescript:
+ specifier: ^6.0.3
+ version: 6.0.3
+
examples/next:
dependencies:
next:
@@ -435,7 +463,7 @@ importers:
version: 1.167.1(@tanstack/react-router@1.170.32(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@tanstack/router-core@1.171.27)(csstype@3.2.3)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
'@tanstack/react-router-ssr-query':
specifier: latest
- version: 1.167.1(@tanstack/query-core@5.101.0)(@tanstack/react-query@5.101.0(react@19.2.6))(@tanstack/react-router@1.170.32(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@tanstack/router-core@1.171.27)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ version: 1.167.2(@tanstack/query-core@5.101.0)(@tanstack/react-query@5.101.0(react@19.2.6))(@tanstack/react-router@1.170.32(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@tanstack/router-core@1.171.27)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
'@tanstack/react-start':
specifier: latest
version: 1.168.49(crossws@0.4.5(srvx@0.11.16))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(vite-plugin-solid@2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.13)(vite@8.0.16(@types/node@22.19.20)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.0.16(@types/node@22.19.20)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
@@ -556,6 +584,18 @@ importers:
specifier: '>=1'
version: 1.9.13
devDependencies:
+ '@nestjs/common':
+ specifier: ^11.2.3
+ version: 11.2.3(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/core':
+ specifier: ^11.2.3
+ version: 11.2.3(@nestjs/common@11.2.3(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.2.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/platform-express':
+ specifier: ^11.2.3
+ version: 11.2.3(@nestjs/common@11.2.3(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.2.3)
+ '@nestjs/testing':
+ specifier: ^11.2.3
+ version: 11.2.3(@nestjs/common@11.2.3(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.2.3)(@nestjs/platform-express@11.2.3)
'@solidjs/testing-library':
specifier: ^0.8.10
version: 0.8.10(solid-js@1.9.13)
@@ -613,6 +653,12 @@ importers:
react-dom:
specifier: ^19.2.6
version: 19.2.6(react@19.2.6)
+ reflect-metadata:
+ specifier: ^0.2.2
+ version: 0.2.2
+ rxjs:
+ specifier: ^7.8.2
+ version: 7.8.2
supertest:
specifier: ^7.2.2
version: 7.2.2
@@ -621,7 +667,7 @@ importers:
version: 5.56.0
svelte-check:
specifier: ^4.5.0
- version: 4.5.0(picomatch@4.0.7)(svelte@5.56.0)(typescript@6.0.3)
+ version: 4.5.0(picomatch@4.0.4)(svelte@5.56.0)(typescript@6.0.3)
tsdown:
specifier: ^0.22.1
version: 0.22.1(tsx@4.22.4)(typescript@6.0.3)
@@ -1252,6 +1298,10 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+ '@lukeed/csprng@1.1.0':
+ resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==}
+ engines: {node: '>=8'}
+
'@mdx-js/mdx@3.1.1':
resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==}
@@ -1273,6 +1323,56 @@ packages:
'@neodrag/core': 3.0.0-next.11
solid-js: ^1.0.0
+ '@nestjs/common@11.2.3':
+ resolution: {integrity: sha512-obdauJXHfthhepbV+LpGe88OeBlR/Kw9lwjLo0Utzc//agoLXYb9DUGhPQWtm81IpBWMv+19eiwcve9MsBZwXA==}
+ peerDependencies:
+ class-transformer: '>=0.4.1'
+ class-validator: '>=0.13.2'
+ reflect-metadata: ^0.1.12 || ^0.2.0
+ rxjs: ^7.1.0
+ peerDependenciesMeta:
+ class-transformer:
+ optional: true
+ class-validator:
+ optional: true
+
+ '@nestjs/core@11.2.3':
+ resolution: {integrity: sha512-vkA9/Ja0Z3hvqXErSa+HaxrfF+cNXthNFi8VPNEKVli4rMd009yExAl0gLmko/Kf8peDXr72u1RN+j9Da2ukHg==}
+ engines: {node: '>= 20'}
+ peerDependencies:
+ '@nestjs/common': ^11.0.0
+ '@nestjs/microservices': ^11.0.0
+ '@nestjs/platform-express': ^11.0.0
+ '@nestjs/websockets': ^11.0.0
+ reflect-metadata: ^0.1.12 || ^0.2.0
+ rxjs: ^7.1.0
+ peerDependenciesMeta:
+ '@nestjs/microservices':
+ optional: true
+ '@nestjs/platform-express':
+ optional: true
+ '@nestjs/websockets':
+ optional: true
+
+ '@nestjs/platform-express@11.2.3':
+ resolution: {integrity: sha512-YFQvRXT2de1qNL9LJPUBQ31+RsfI4cJ+sbpU9ENM/hDCgoHSEhm7oxUuGGKmhTZBNZEYm8mDYdfoTFmAH1LIJg==}
+ peerDependencies:
+ '@nestjs/common': ^11.0.0
+ '@nestjs/core': ^11.0.0
+
+ '@nestjs/testing@11.2.3':
+ resolution: {integrity: sha512-7ANDWlkm8Xw4CYIhCNZhtBzANsQUKqjteA2yx/6sjqGyWhekeBKz8wgCJykm0vo+ltrg6U34dZlm2NgiRcNHPQ==}
+ peerDependencies:
+ '@nestjs/common': ^11.0.0
+ '@nestjs/core': ^11.0.0
+ '@nestjs/microservices': ^11.0.0
+ '@nestjs/platform-express': ^11.0.0
+ peerDependenciesMeta:
+ '@nestjs/microservices':
+ optional: true
+ '@nestjs/platform-express':
+ optional: true
+
'@next/env@16.2.6':
resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==}
@@ -2634,12 +2734,12 @@ packages:
'@tanstack/router-core':
optional: true
- '@tanstack/react-router-ssr-query@1.167.1':
- resolution: {integrity: sha512-W9j5JPnBikyafvuUfykFfHIWod58OAbAAa5leNkXBcoDoocghMmu6w9uZOmUZvAWT7CSvgj5tBUtF7CM2OoHXQ==}
+ '@tanstack/react-router-ssr-query@1.167.2':
+ resolution: {integrity: sha512-yRvy0VJ00R8huPuquk+qyYQGMpYRc/G33mc6/yZ4f7LaBZz9h/VbACjmLzhm/+6u5WSmUt6ouJJZnI1/5Npfag==}
engines: {node: '>=20.19'}
peerDependencies:
- '@tanstack/query-core': '>=5.90.0'
- '@tanstack/react-query': '>=5.90.0'
+ '@tanstack/query-core': '>=5.102.0'
+ '@tanstack/react-query': '>=5.102.0'
'@tanstack/react-router': '>=1.127.0'
react: '>=18.0.0 || >=19.0.0'
react-dom: '>=18.0.0 || >=19.0.0'
@@ -2927,11 +3027,11 @@ packages:
webpack:
optional: true
- '@tanstack/router-ssr-query-core@1.169.1':
- resolution: {integrity: sha512-rngux8s/3mPQzcjLYDLkNU31coYVyCgrVTfpdwqUdY5jIEHqGTXrO73DTkPR1PppwYUeVhmNCgl8TctRcnupjg==}
+ '@tanstack/router-ssr-query-core@1.169.2':
+ resolution: {integrity: sha512-7pO65Aiq/1+aS3Mb6vSGtIjzQ/YGv9JTfBbn2EYlHcZnJ4s4ZGCeTPI847geB1RfJqVhfwxM8bZgGM51mYkolg==}
engines: {node: '>=20.19'}
peerDependencies:
- '@tanstack/query-core': '>=5.90.0'
+ '@tanstack/query-core': '>=5.102.0'
'@tanstack/router-core': '>=1.127.0'
'@tanstack/router-utils@1.162.1':
@@ -3568,6 +3668,9 @@ packages:
resolution: {integrity: sha512-44mvgtPvohuU/70DdY5Oz2AIrLJ9k6/5x4KmoSvPwO+5Moijo0+N9D0fKbbYZQWP1hNm5CpOf+E01jhxG/r8xg==}
engines: {node: '>=14'}
+ append-field@1.0.0:
+ resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==}
+
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
@@ -3668,6 +3771,13 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
+ buffer-from@1.1.2:
+ resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+
+ busboy@1.6.0:
+ resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
+ engines: {node: '>=10.16.0'}
+
bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'}
@@ -3786,6 +3896,10 @@ packages:
compute-scroll-into-view@3.1.1:
resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==}
+ concat-stream@2.0.0:
+ resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==}
+ engines: {'0': node >= 6.0}
+
confbox@0.1.8:
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
@@ -4514,6 +4628,10 @@ packages:
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
engines: {node: '>=18'}
+ file-type@21.3.4:
+ resolution: {integrity: sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==}
+ engines: {node: '>=20'}
+
file-type@22.0.1:
resolution: {integrity: sha512-ww5Mhre0EE+jmBvOXTmXAbEMuZE7uX4a3+oRCQFNj8w++g3ev913N6tXQz0XTXbueQ5TWQfm6BdaViEHHn8bhA==}
engines: {node: '>=22'}
@@ -5000,6 +5118,10 @@ packages:
resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
engines: {node: '>=8'}
+ iterare@1.2.1:
+ resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==}
+ engines: {node: '>=6'}
+
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
@@ -5154,6 +5276,10 @@ packages:
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
engines: {node: '>= 12.0.0'}
+ load-esm@1.0.3:
+ resolution: {integrity: sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==}
+ engines: {node: '>=13.2.0'}
+
locate-character@3.0.0:
resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
@@ -5271,6 +5397,10 @@ packages:
mdn-data@2.27.1:
resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
+ media-typer@0.3.0:
+ resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
+ engines: {node: '>= 0.6'}
+
media-typer@1.1.0:
resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
engines: {node: '>= 0.8'}
@@ -5484,6 +5614,10 @@ packages:
muggle-string@0.4.1:
resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
+ multer@2.2.0:
+ resolution: {integrity: sha512-6rdyFg2kLrMh9Jee7/BMPuV9lEAd7lLW2YUpF9/YxR7njyoUwwQ0ZPh3TaIY50Sw6vlyD2HW3wGOkTS4P79xrQ==}
+ engines: {node: '>= 10.16.0'}
+
nanoid@3.3.12:
resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -5909,6 +6043,10 @@ packages:
resolution: {integrity: sha512-PNaGjoCnw9DBA2Kl8D+8po957z778q/HOPuY2u3Bkw/JO3eC8MDx7jn/PgMtSgpcBbs+6UOjDbwReGpXmRvs0g==}
engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0}
+ readable-stream@3.6.2:
+ resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
+ engines: {node: '>= 6'}
+
readdirp@4.1.2:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
@@ -5942,6 +6080,9 @@ packages:
resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
engines: {node: '>=8'}
+ reflect-metadata@0.2.2:
+ resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
+
regex-recursion@6.0.2:
resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
@@ -6048,10 +6189,16 @@ packages:
rw@1.3.3:
resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
+ rxjs@7.8.2:
+ resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
+
sade@1.8.1:
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
engines: {node: '>=6'}
+ safe-buffer@5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
safe-regex2@5.1.1:
resolution: {integrity: sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==}
hasBin: true
@@ -6220,6 +6367,10 @@ packages:
std-env@4.1.0:
resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==}
+ streamsearch@1.1.0:
+ resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
+ engines: {node: '>=10.0.0'}
+
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -6232,6 +6383,9 @@ packages:
resolution: {integrity: sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==}
engines: {node: '>=20'}
+ string_decoder@1.3.0:
+ resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+
stringify-entities@4.0.4:
resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
@@ -6473,10 +6627,17 @@ packages:
resolution: {integrity: sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==}
engines: {node: '>=20'}
+ type-is@1.6.18:
+ resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
+ engines: {node: '>= 0.6'}
+
type-is@2.1.0:
resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==}
engines: {node: '>= 18'}
+ typedarray@0.0.6:
+ resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
+
typescript@6.0.3:
resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==}
engines: {node: '>=14.17'}
@@ -6485,6 +6646,10 @@ packages:
ufo@1.6.4:
resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==}
+ uid@2.0.2:
+ resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==}
+ engines: {node: '>=8'}
+
uint8array-extras@1.5.0:
resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==}
engines: {node: '>=18'}
@@ -7449,6 +7614,8 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
+ '@lukeed/csprng@1.1.0': {}
+
'@mdx-js/mdx@3.1.1':
dependencies:
'@types/estree': 1.0.9
@@ -7497,6 +7664,51 @@ snapshots:
'@neodrag/core': 3.0.0-next.11
solid-js: 1.9.13
+ '@nestjs/common@11.2.3(reflect-metadata@0.2.2)(rxjs@7.8.2)':
+ dependencies:
+ file-type: 21.3.4
+ iterare: 1.2.1
+ load-esm: 1.0.3
+ reflect-metadata: 0.2.2
+ rxjs: 7.8.2
+ tslib: 2.8.1
+ uid: 2.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@nestjs/core@11.2.3(@nestjs/common@11.2.3(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.2.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)':
+ dependencies:
+ '@nestjs/common': 11.2.3(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ fast-safe-stringify: 2.1.1
+ iterare: 1.2.1
+ path-to-regexp: 8.4.2
+ reflect-metadata: 0.2.2
+ rxjs: 7.8.2
+ tslib: 2.8.1
+ uid: 2.0.2
+ optionalDependencies:
+ '@nestjs/platform-express': 11.2.3(@nestjs/common@11.2.3(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.2.3)
+
+ '@nestjs/platform-express@11.2.3(@nestjs/common@11.2.3(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.2.3)':
+ dependencies:
+ '@nestjs/common': 11.2.3(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/core': 11.2.3(@nestjs/common@11.2.3(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.2.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ cors: 2.8.6
+ express: 5.2.1
+ multer: 2.2.0
+ path-to-regexp: 8.4.2
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@nestjs/testing@11.2.3(@nestjs/common@11.2.3(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.2.3)(@nestjs/platform-express@11.2.3)':
+ dependencies:
+ '@nestjs/common': 11.2.3(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/core': 11.2.3(@nestjs/common@11.2.3(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.2.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ tslib: 2.8.1
+ optionalDependencies:
+ '@nestjs/platform-express': 11.2.3(@nestjs/common@11.2.3(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.2.3)
+
'@next/env@16.2.6': {}
'@next/swc-darwin-arm64@16.2.6':
@@ -8665,12 +8877,12 @@ snapshots:
transitivePeerDependencies:
- csstype
- '@tanstack/react-router-ssr-query@1.167.1(@tanstack/query-core@5.101.0)(@tanstack/react-query@5.101.0(react@19.2.6))(@tanstack/react-router@1.170.32(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@tanstack/router-core@1.171.27)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ '@tanstack/react-router-ssr-query@1.167.2(@tanstack/query-core@5.101.0)(@tanstack/react-query@5.101.0(react@19.2.6))(@tanstack/react-router@1.170.32(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@tanstack/router-core@1.171.27)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
dependencies:
'@tanstack/query-core': 5.101.0
'@tanstack/react-query': 5.101.0(react@19.2.6)
'@tanstack/react-router': 1.170.32(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@tanstack/router-ssr-query-core': 1.169.1(@tanstack/query-core@5.101.0)(@tanstack/router-core@1.171.27)
+ '@tanstack/router-ssr-query-core': 1.169.2(@tanstack/query-core@5.101.0)(@tanstack/router-core@1.171.27)
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
transitivePeerDependencies:
@@ -9062,7 +9274,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@tanstack/router-ssr-query-core@1.169.1(@tanstack/query-core@5.101.0)(@tanstack/router-core@1.171.27)':
+ '@tanstack/router-ssr-query-core@1.169.2(@tanstack/query-core@5.101.0)(@tanstack/router-core@1.171.27)':
dependencies:
'@tanstack/query-core': 5.101.0
'@tanstack/router-core': 1.171.27
@@ -9862,6 +10074,8 @@ snapshots:
ansis@4.3.0: {}
+ append-field@1.0.0: {}
+
argparse@2.0.1: {}
aria-hidden@1.2.6:
@@ -9972,6 +10186,12 @@ snapshots:
node-releases: 2.0.46
update-browserslist-db: 1.2.3(browserslist@4.28.2)
+ buffer-from@1.1.2: {}
+
+ busboy@1.6.0:
+ dependencies:
+ streamsearch: 1.1.0
+
bytes@3.1.2: {}
cac@6.7.14: {}
@@ -10063,6 +10283,13 @@ snapshots:
compute-scroll-into-view@3.1.1: {}
+ concat-stream@2.0.0:
+ dependencies:
+ buffer-from: 1.1.2
+ inherits: 2.0.4
+ readable-stream: 3.6.2
+ typedarray: 0.0.6
+
confbox@0.1.8: {}
config-chain@1.1.13:
@@ -10708,6 +10935,15 @@ snapshots:
dependencies:
is-unicode-supported: 2.1.0
+ file-type@21.3.4:
+ dependencies:
+ '@tokenizer/inflate': 0.4.1
+ strtok3: 10.3.5
+ token-types: 6.1.2
+ uint8array-extras: 1.5.0
+ transitivePeerDependencies:
+ - supports-color
+
file-type@22.0.1:
dependencies:
'@tokenizer/inflate': 0.4.1
@@ -11246,6 +11482,8 @@ snapshots:
html-escaper: 2.0.2
istanbul-lib-report: 3.0.1
+ iterare@1.2.1: {}
+
jackspeak@3.4.3:
dependencies:
'@isaacs/cliui': 8.0.2
@@ -11385,6 +11623,8 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.32.0
lightningcss-win32-x64-msvc: 1.32.0
+ load-esm@1.0.3: {}
+
locate-character@3.0.0: {}
lodash-es@4.18.1: {}
@@ -11611,6 +11851,8 @@ snapshots:
mdn-data@2.27.1: {}
+ media-typer@0.3.0: {}
+
media-typer@1.1.0: {}
memoirist@0.4.0: {}
@@ -11973,6 +12215,13 @@ snapshots:
muggle-string@0.4.1: {}
+ multer@2.2.0:
+ dependencies:
+ append-field: 1.0.0
+ busboy: 1.6.0
+ concat-stream: 2.0.0
+ type-is: 1.6.18
+
nanoid@3.3.12: {}
negotiator@1.0.0: {}
@@ -12039,7 +12288,7 @@ snapshots:
postcss: 8.4.31
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
- styled-jsx: 5.1.6(@babel/core@7.29.7)(react@19.2.6)
+ styled-jsx: 5.1.6(react@19.2.6)
optionalDependencies:
'@next/swc-darwin-arm64': 16.2.6
'@next/swc-darwin-x64': 16.2.6
@@ -12495,6 +12744,12 @@ snapshots:
json-parse-even-better-errors: 6.0.0
npm-normalize-package-bin: 6.0.0
+ readable-stream@3.6.2:
+ dependencies:
+ inherits: 2.0.4
+ string_decoder: 1.3.0
+ util-deprecate: 1.0.2
+
readdirp@4.1.2: {}
readdirp@5.0.0: {}
@@ -12537,6 +12792,8 @@ snapshots:
indent-string: 4.0.0
strip-indent: 3.0.0
+ reflect-metadata@0.2.2: {}
+
regex-recursion@6.0.2:
dependencies:
regex-utilities: 2.3.0
@@ -12694,10 +12951,16 @@ snapshots:
rw@1.3.3: {}
+ rxjs@7.8.2:
+ dependencies:
+ tslib: 2.8.1
+
sade@1.8.1:
dependencies:
mri: 1.2.0
+ safe-buffer@5.2.1: {}
+
safe-regex2@5.1.1:
dependencies:
ret: 0.5.0
@@ -12898,6 +13161,8 @@ snapshots:
std-env@4.1.0: {}
+ streamsearch@1.1.0: {}
+
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
@@ -12915,6 +13180,10 @@ snapshots:
get-east-asian-width: 1.6.0
strip-ansi: 7.2.0
+ string_decoder@1.3.0:
+ dependencies:
+ safe-buffer: 5.2.1
+
stringify-entities@4.0.4:
dependencies:
character-entities-html4: 2.1.0
@@ -12958,6 +13227,12 @@ snapshots:
client-only: 0.0.1
react: 19.2.4
+ styled-jsx@5.1.6(react@19.2.6):
+ dependencies:
+ client-only: 0.0.1
+ react: 19.2.6
+ optional: true
+
stylis@4.4.0: {}
superagent@10.3.0:
@@ -12986,6 +13261,18 @@ snapshots:
dependencies:
has-flag: 4.0.0
+ svelte-check@4.5.0(picomatch@4.0.4)(svelte@5.56.0)(typescript@6.0.3):
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.31
+ chokidar: 4.0.3
+ fdir: 6.5.0(picomatch@4.0.4)
+ picocolors: 1.1.1
+ sade: 1.8.1
+ svelte: 5.56.0
+ typescript: 6.0.3
+ transitivePeerDependencies:
+ - picomatch
+
svelte-check@4.5.0(picomatch@4.0.7)(svelte@5.56.0)(typescript@6.0.3):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
@@ -13169,16 +13456,27 @@ snapshots:
dependencies:
tagged-tag: 1.0.0
+ type-is@1.6.18:
+ dependencies:
+ media-typer: 0.3.0
+ mime-types: 2.1.35
+
type-is@2.1.0:
dependencies:
content-type: 2.0.0
media-typer: 1.1.0
mime-types: 3.0.2
+ typedarray@0.0.6: {}
+
typescript@6.0.3: {}
ufo@1.6.4: {}
+ uid@2.0.2:
+ dependencies:
+ '@lukeed/csprng': 1.1.0
+
uint8array-extras@1.5.0: {}
ultracite@7.10.6(oxfmt@0.64.0(svelte@5.56.0))(oxlint@1.80.0(oxlint-tsgolint@7.0.2001)):