diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..6e4ce0ce --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +**/node_modules/ +**/dist/ +**/.turbo/ +**/coverage/ +**/*.log +.git/ diff --git a/.github/instructions/general.instructions.md b/.github/instructions/general.instructions.md index b3dcab72..bb6c8886 100644 --- a/.github/instructions/general.instructions.md +++ b/.github/instructions/general.instructions.md @@ -15,6 +15,15 @@ When defining functions, use an object for arguments instead of individual param Prefer using functions instead of classes for better simplicity and composability. +## Naming Conventions + +- User kebab-case for folder names (e.g., `my-folder`). +- Use camelCase for file names (e.g., `myFile.ts`). +- Use camelCase for variable and function names (e.g., `myVariable`, `myFunction`). +- Use PascalCase for type and interface names (e.g., `MyType`, `MyInterface`). +- Use uppercase with underscores for constants (e.g., `MY_CONSTANT`). +- For test files, use the same name as the file being tested with `.test` appended before the extension (e.g., `myFile.test.ts`). + ## Linting To fix ESLint issues in a specific file, run `pnpm eslint --fix path/to/file`. @@ -25,4 +34,20 @@ If you need to change the database schema, read the instructions in `packages/po ## Documentation -All documentation must be written in English. +All documentation must be written in English. The target audience is developers with technical skills — write concisely and precisely, assume familiarity with REST APIs, JWT, and common backend concepts, and prefer code examples over prose descriptions. + +## Implementation Checklist + +Every implementation — whether adding a new feature or changing existing behavior — must complete all of the following steps before being considered done: + +1. **Implement business logic** — Write or update code in `packages/server/src/lib/.ts`. All database access goes here; route handlers must stay free of direct DB calls. + +2. **REST API** — Add or update route handlers in `packages/server/src/rest/v1/.ts`. Every handler must have an `@openapi` JSDoc block and the corresponding OpenAPI spec in `packages/server/src/rest/openapi/v1/.yaml` must be kept in sync. + +3. **Module docs** — Update the module documentation page at `packages/website/docs/modules/.md`, including any changes to the data model, key concepts, or the `## Permissions` table. + +4. **MCP tool** (project-scoped changes only) — If the change affects a resource that is exposed through the MCP server, add or update the tool in `packages/server/src/mcp/tools/.ts` and ensure it is registered in `packages/server/src/mcp/tools/index.ts`. + +5. **Tests** — Add or update tests in `packages/server/tests/unit/tests/.test.ts`. Every new route and every changed lib function must have coverage (happy path, `401`, `403`, and relevant edge cases). + +6. **Smoke test** (when applicable) — If the change introduces a new user-facing flow (e.g., a new resource lifecycle), add the corresponding steps to `tests/smoke-test.sh`. Run it with `pnpm run -w smoke-test` to verify end-to-end behaviour against a live server. diff --git a/.github/instructions/modules.instructions.md b/.github/instructions/modules.instructions.md new file mode 100644 index 00000000..8525c8eb --- /dev/null +++ b/.github/instructions/modules.instructions.md @@ -0,0 +1,44 @@ +--- +applyTo: '**' +description: Instructions for creating and maintaining modules across the codebase. +--- + +# Module Instructions + +A module is a named resource (e.g., `files`, `users`) that is exposed through the REST API, the MCP server, and documented in the website. Whenever a module is created or changed, **all four areas must be updated together**: + +1. **REST** — route handlers and OpenAPI spec +2. **MCP** — tool definitions in the MCP server +3. **Docs** — module documentation in the website +4. **Tests** — unit tests covering the new or changed behavior + +## Checklist for Every Module Change + +- [ ] Business logic updated in `packages/server/src/lib/.ts` +- [ ] REST routes updated in `packages/server/src/rest/v1/.ts` with `@openapi` JSDoc blocks +- [ ] Module router registered in `packages/server/src/rest/v1/index.ts` +- [ ] MCP tools updated in `packages/server/src/mcp/tools/.ts` +- [ ] Module docs updated in `packages/website/docs/modules/.md` +- [ ] Tests updated in `packages/server/tests/unit/tests/.test.ts` + +## REST + +Follow the rules in `server.instructions.md`. Each module gets its own file under `src/rest/v1/.ts` and must be mounted in `src/rest/v1/index.ts`. + +## MCP + +Each module operation must be exposed as an MCP tool in its own file at `src/mcp/tools/.ts`. The file must export a `registerTools` function that accepts a `McpServer` instance. It must then be imported and called in `src/mcp/tools/index.ts`. Tool names follow the pattern `-` (e.g., `list-files`, `create-user`). Tools call REST endpoints via `apiCall` using the same paths defined in the REST routes. + +## Docs + +Each module has a dedicated documentation page at `packages/website/docs/modules/.md`. The page must describe: + +- What the module does (overview) +- Key concepts and data model +- Any roles or access rules that apply + +Do **not** document REST endpoints in the module docs — those are covered in the auto-generated API reference. + +## Tests + +Tests live in `packages/server/tests/unit/tests/.test.ts`. Every public lib function and every REST route must have at least one test. Follow the patterns already established in `files.test.ts` and `users.test.ts`. diff --git a/.github/instructions/postgresdb.instructions.md b/.github/instructions/postgresdb.instructions.md index 5b29b7ab..c4f04a1c 100644 --- a/.github/instructions/postgresdb.instructions.md +++ b/.github/instructions/postgresdb.instructions.md @@ -8,3 +8,19 @@ description: Instructions for the PostgresDB package usage and integration. Check `#fetch https://ttoss.dev/docs/modules/packages/postgresdb/` for the official documentation of the `@ttoss/postgresdb` package used in this module. If you modify the database schema, ensure to make the tests pass by running `pnpm test` in the `packages/postgresdb`. + +## Public ID + +All models must have a `publicId` column (see `src/utils/publicId.ts`). The `publicId` is the only identifier exposed to external consumers. The internal `id` (UUID primary key) is for database-level joins only and must never be returned through any API or tool. + +When adding a new model, register a corresponding prefix in `src/utils/publicId.ts` (e.g., `user: 'usr_'`) and use it in the model's `beforeValidate` hook via `generatePublicId`. + +## Rebuilding After Model Changes + +After adding or modifying a model, rebuild the package so dependents (e.g., `@soat/server`) pick up the updated types: + +```bash +pnpm --filter @soat/postgresdb build +``` + +Without this step, TypeScript in the server package will report errors like `Property 'User' does not exist on type`. diff --git a/.github/instructions/server.instructions.md b/.github/instructions/server.instructions.md index dc4f40a0..969fdebb 100644 --- a/.github/instructions/server.instructions.md +++ b/.github/instructions/server.instructions.md @@ -12,30 +12,85 @@ Follow the packages documentation: ## Architecture -The server src will have two folders: rest and mcp. +The server src will have three folders: rest, mcp, and lib. + +### Business Logic Layer + +All business logic (database queries, data transformations) must live in `src/lib/`, organized by resource: + +- `src/lib/files.ts` - All file-related business logic (listFiles, getFile, createFile, deleteFile) +- Additional resources follow the same pattern: `src/lib/.ts` + +Route handlers **must not** contain direct database calls. They are responsible only for HTTP concerns: parsing request bodies/params, calling lib functions, and setting response status/body. + +Lib functions **must always return plain mapped objects**, never raw model instances. This is required to avoid exposing sensitive or internal data (e.g., internal DB fields, hashed passwords, audit columns) through the API. Every function that queries the database must map the result to a plain object before returning it: + +### Public ID as `id` + +The internal database `id` (primary key) **must never be returned to the user**. Always expose `publicId` as `id` in API responses: + +```ts +export const getFile = async (args: { id: string }) => { + const file = await db.File.findOne({ where: { publicId: args.id } }); + if (!file) return null; + return { + id: file.publicId, // publicId is exposed as `id` + filename: file.filename, + // ... all fields explicitly mapped, internal `id` is never included + }; +}; +``` + +- Route parameters and query inputs that reference a resource by ID will always be `publicId` values. +- The database `id` column is for internal joins only and must not appear in any API response, OpenAPI schema, or MCP tool output. ### REST API Structure The REST API is organized by version and resource for better maintainability and versioning: -- `src/rest/v1/documents.ts` - Contains all document-related endpoints and handlers for API version 1 -- Future versions will follow the same pattern: `src/rest/v2/documents.ts`, etc. +- `src/rest/v1/files.ts` - Contains all file-related endpoints and handlers for API version 1 +- Future versions will follow the same pattern: `src/rest/v2/files.ts`, etc. -Each version folder contains resource-specific files. Currently, only the documents resource is implemented, but additional resources can be added as separate files (e.g., `users.ts`, `analytics.ts`) within each version folder. +Each version folder contains resource-specific files. Additional resources can be added as separate files (e.g., `users.ts`, `analytics.ts`) within each version folder. #### Router Organization API routes are not defined directly in `src/index.ts` to maintain clean separation of concerns: - `src/rest/router.ts` - Central REST API router that imports and mounts versioned routers -- Version-specific routers (e.g., `src/rest/v1/documents.ts`) define the actual route handlers +- Version-specific routers (e.g., `src/rest/v1/files.ts`) define the actual route handlers - `src/index.ts` remains focused on application setup, middleware, and mounting the main routers This structure ensures scalability and keeps the main entry point uncluttered as the API grows. +#### Swagger JSDoc + +Every route handler **must** have an `@openapi` JSDoc block immediately before it. The JSDoc must match the handler's actual behavior (paths, status codes, request/response shapes). + +```ts +/** + * @openapi + * /files: + * get: + * tags: + * - Files + * summary: List all files + * operationId: listFiles + * responses: + * '200': + * description: ... + */ +filesRouter.get('/files', async (ctx: Context) => { ... }); +``` + #### OpenAPI Documentation -When modifying or adding REST API endpoints, **always update the corresponding OpenAPI specification** in `src/rest/openapi/` and follow the guidelines outlined in `src/rest/openapi/README.md`. This includes: +When modifying or adding REST API endpoints, **always update both**: + +1. The `@openapi` JSDoc block on the route handler +2. The corresponding OpenAPI specification in `src/rest/openapi/v1/.yaml` + +Follow the guidelines in `src/rest/openapi/README.md`. This includes: - Updating paths, schemas, request/response bodies, and error responses - Adding descriptive examples and operation IDs @@ -57,23 +112,6 @@ The MCP (Model Context Protocol) folder is organized to separate concerns and ma This structure allows for easy addition of new tools and resources while keeping the code organized and maintainable. -### Core Functionality Guidelines - -**Important**: Core business logic and functionalities must be implemented in dedicated core packages (e.g., `@soat/documents-core`, `@soat/text-atomizer`) and never directly in the server folder. - -The server package should only contain: - -- HTTP routing and request handling -- Middleware configuration -- Integration with core packages -- API versioning and structure - -This separation ensures: - -- Reusability of core logic across different interfaces (CLI, web, etc.) -- Better testability of business logic -- Cleaner architecture with clear boundaries - ## Development To run the server in development mode with watch, navigate to the server package and run: @@ -102,91 +140,110 @@ pnpm build This uses `tsup` to compile the TypeScript code. -## Manual Testing +## Testing -### REST API Endpoints +### Running Tests -To test REST API endpoints during development: +Run all server tests from the repo root: -1. **Start the development server:** +```bash +pnpm --filter @soat/server test +``` - ```bash - cd packages/server - pnpm dev - ``` +Run tests for a specific file using `--testPathPatterns` (plural): -2. **Make requests using curl to `0.0.0.0:5047`:** +```bash +pnpm --filter @soat/server test --testPathPatterns=users.test.ts +``` - **Important:** Always use `0.0.0.0` instead of `localhost` when making curl requests to avoid connection issues. +Do **not** use `npx jest` directly or `--testPathPattern` (singular). - Example endpoints: +### Test File Location and Naming - ```bash - # List files - curl -X GET http://0.0.0.0:5047/api/v1/files +- Server unit tests live in `packages/server/tests/unit/tests/` +- Test file name must match the module: `.test.ts` (e.g., `projects.test.ts`) +- Every public lib function and every REST route must have at least one test - # Upload a file - curl -X POST http://0.0.0.0:5047/api/v1/files/upload \ - -H "Content-Type: application/json" \ - -d '{"content":"Hello World!","options":{"contentType":"text/plain","metadata":{"filename":"test.txt"}}}' +### Test Infrastructure - # Get file by ID - curl -X GET http://0.0.0.0:5047/api/v1/files/{file-id} +Tests are integration tests that run against `app.callback()` via supertest. A real PostgreSQL instance is spun up via testcontainers, configured in `setupTestsAfterEnv.ts`. No mocking of the database layer is needed. - # Delete file - curl -X DELETE http://0.0.0.0:5047/api/v1/files/{file-id} - ``` +#### Helpers (from `tests/unit/testClient.ts`) -## Unit Testing +- `testClient` — unauthenticated supertest client +- `authenticatedTestClient(token)` — returns a client that sets `Authorization: Bearer ` on every request +- `loginAs(username, password)` — bootstrap helper that logs in and returns the token string -Unit tests are located in the #file:../../packages/server/tests/unit/ folder. To run the unit tests for the server package, use the following command from the root directory: +For API key authentication, pass the raw `SDK_`-prefixed key directly to `authenticatedTestClient`. -```bash -pnpm --filter @soat/server test -``` +### Writing Unit Tests + +Group tests by HTTP method and route path using nested `describe` blocks: + +```ts +describe('MyModule', () => { + let adminToken: string; + let userToken: string; + + beforeAll(async () => { + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + adminToken = await loginAs('admin', 'supersecret'); + + await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'alice', password: 'alicepass' }); -### REST API Tests - -REST API tests are located in the #file:../../packages/server/tests/unit/tests/rest/ folder. These tests cover the various endpoints and functionalities of the REST API. - -- Each endpoint has corresponding test files that validate the expected behavior. -- Use mocks on #file:../../packages/server/tests/unit/setupTests.ts to mock external dependencies only and isolate the tests. -- Test the whole app, #file:../../packages/server/src/app.ts , to ensure all middleware and routes are properly integrated. Use supertest to simulate HTTP requests and validate responses. - In the example below, `saveFile` from `@soat/files-core` is mocked to test the file upload endpoint without actually saving a file. - - ```ts - import { saveFile } from '@soat/files-core'; - import { app } from 'src/app'; - import request from 'supertest'; - - test('should create a file via REST API', async () => { - const savedFile = { - id: 'test-id', - filename: 'test.txt', - content: 'Hello, World!', - metadata: {}, - }; - - jest.mocked(saveFile).mockResolvedValue(savedFile); - - const response = await request(app.callback()) - .post('/api/v1/files/upload') - .send({ - content: 'Hello, World!', - options: { metadata: { filename: 'test.txt' } }, - }) - .set('Accept', 'application/json'); - - expect(response.status).toBe(201); - expect(response.body).toEqual({ - id: 'test-id', - filename: 'test.txt', - success: true, + userToken = await loginAs('alice', 'alicepass'); + }); + + describe('GET /api/v1/resource', () => { + test('authenticated user can list resources', async () => { + const response = + await authenticatedTestClient(userToken).get('/api/v1/resource'); + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); }); - expect(saveFile).toHaveBeenCalledWith({ - config: { local: { path: '/tmp/files' }, type: 'local' }, - content: 'Hello, World!', - options: { metadata: { filename: 'test.txt' } }, + + test('unauthenticated request returns 401', async () => { + const response = await testClient.get('/api/v1/resource'); + expect(response.status).toBe(401); }); }); - ``` +}); +``` + +Every module must cover: + +- Happy path for each route (correct status code and response shape) +- `401` for unauthenticated requests +- `403` for requests by users without required permission +- Edge cases specific to the business logic (e.g., API key scoping, missing resources returning `404`) + +Always assert the shape of the response body, not just the status code: + +```ts +expect(response.body.id).toBeDefined(); +expect(response.body.name).toBe('expected name'); +expect(response.body.password).toBeUndefined(); // sensitive fields must be absent +``` + +Internal database IDs must never appear in responses — assert `id` maps to `publicId`. + +### Manual Testing (curl) + +You can run the server in dev mode and test endpoints manually with curl: + +```bash +pnpm run -w dev +``` + +When the dev server is running, use `0.0.0.0` (not `localhost`) for curl requests: + +```bash +curl -X GET http://0.0.0.0:5047/api/v1/files +curl -X GET http://0.0.0.0:5047/api/v1/files/{file-id} +curl -X DELETE http://0.0.0.0:5047/api/v1/files/{file-id} +``` diff --git a/.github/instructions/tests.instructions.md b/.github/instructions/tests.instructions.md new file mode 100644 index 00000000..8c1422cb --- /dev/null +++ b/.github/instructions/tests.instructions.md @@ -0,0 +1,114 @@ +--- +applyTo: '**' +description: Instructions for writing, running, and maintaining unit tests across the codebase. +--- + +# Test Instructions + +## Running Tests + +Run all tests for a package from the repo root: + +```bash +pnpm --filter @soat/server test +``` + +Run tests for a specific file using `--testPathPatterns` (plural): + +```bash +pnpm --filter @soat/server test --testPathPatterns=users.test.ts +``` + +Do **not** use `npx jest` directly or `--testPathPattern` (singular). + +## Test File Location and Naming + +- Server unit tests live in `packages/server/tests/unit/tests/` +- Test file name must match the module: `.test.ts` (e.g., `projects.test.ts`) +- Every public lib function and every REST route must have at least one test + +## Test Infrastructure + +Tests are integration tests that run against `app.callback()` via supertest. A real PostgreSQL instance is spun up via testcontainers, configured in `setupTestsAfterEnv.ts`. No mocking of the database layer is needed. + +### Helpers (from `tests/unit/testClient.ts`) + +- `testClient` — unauthenticated supertest client +- `authenticatedTestClient(token)` — returns a client that sets `Authorization: Bearer ` on every request +- `loginAs(username, password)` — bootstrap helper that logs in and returns the token string + +For API key authentication, pass the raw `SDK_`-prefixed key directly to `authenticatedTestClient`. + +## Writing Unit Tests + +### Structure + +Group tests by HTTP method and route path using nested `describe` blocks: + +```ts +describe('MyModule', () => { + let adminToken: string; + let userToken: string; + + beforeAll(async () => { + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + adminToken = await loginAs('admin', 'supersecret'); + + await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'alice', password: 'alicepass' }); + + userToken = await loginAs('alice', 'alicepass'); + }); + + describe('GET /api/v1/resource', () => { + test('authenticated user can list resources', async () => { + const response = + await authenticatedTestClient(userToken).get('/api/v1/resource'); + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient.get('/api/v1/resource'); + expect(response.status).toBe(401); + }); + }); +}); +``` + +### Coverage Requirements + +Every module must cover: + +- Happy path for each route (correct status code and response shape) +- `401` for unauthenticated requests +- `403` for requests by users without required permission +- Edge cases specific to the business logic (e.g., API key scoping, missing resources returning `404`) + +### Response Shape Assertions + +Always assert the shape of the response body, not just the status code: + +```ts +expect(response.body.id).toBeDefined(); +expect(response.body.name).toBe('expected name'); +expect(response.body.password).toBeUndefined(); // sensitive fields must be absent +``` + +Internal database IDs must never appear in responses — assert `id` maps to `publicId`. + +## Smoke Test + +The smoke test (`tests/smoke-test.sh`) is an end-to-end shell script that runs against a live server. It requires `curl` and `jq`. + +### Running + +```bash +pnpm run -w smoke-test +``` + +The script uses `set -e` and exits with a non-zero code on the first failure, printing which step failed. diff --git a/.github/instructions/website.instructions.md b/.github/instructions/website.instructions.md index f1697b4d..1dc10d71 100644 --- a/.github/instructions/website.instructions.md +++ b/.github/instructions/website.instructions.md @@ -22,6 +22,11 @@ Agents working on the website package must adhere to the following guidelines to - **Tone and Voice**: Write in a technical, confident, and concise manner as specified in the BRANDBOOK.md. Avoid unnecessary jargon while maintaining technical accuracy. - **Navigation**: Ensure all new pages and sections are properly integrated into the navigation system and sidebars. +## Module Documentation + +- **Permission Actions**: Each module doc owns its permission table in a `## Permissions` section. The table must have four columns: **Action**, **Permission**, **REST Endpoint**, and **MCP Tool**. `iam.md` explains the `resource:Action` format and wildcards but does **not** list individual actions — it links to each module's `## Permissions` section instead. +- **Keep in sync**: When a new permission action is added to the server, add a row to the relevant module doc's permissions table and, if it introduces a new module, add a link in `iam.md`. + ## Technical Requirements - **API Documentation**: When generating or updating API documentation, ensure it is comprehensive and follows the standards outlined in the website package. diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 00000000..572a8669 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,102 @@ +name: PR + +on: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + build-test-and-deploy: + name: Build, Test and Deploy + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + - uses: actions/checkout@v6 + + - uses: pnpm/action-setup@v5 + + - uses: actions/setup-node@v6 + with: + node-version: '24' + cache: 'pnpm' + + - name: Cache Turbo + uses: actions/cache@v5 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo- + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v6 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + - name: Build, test and deploy + run: pnpm turbo run deploy + + - name: Deploy report + run: pnpm turbo run deploy-report + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + CARLIN_BRANCH: ${{ github.event.pull_request.head.ref }} + + smoke-test: + name: Smoke Tests + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - uses: pnpm/action-setup@v5 + + - uses: actions/setup-node@v6 + with: + node-version: '24' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Cache Ollama models + uses: actions/cache@v5 + with: + path: ~/.ollama/models + key: ollama-models-qwen3-embedding-0.6b-qwen2.5-0.5b + + - name: Run smoke tests + run: pnpm run smoke-test + env: + COMPOSE_BAKE: '1' + + all-checks: + name: All Checks Passed + runs-on: ubuntu-latest + needs: + - build-test-and-deploy + - smoke-test + if: always() + + steps: + - name: Check all jobs passed + run: | + if [[ "${{ needs.build-test-and-deploy.result }}" != "success" || \ + "${{ needs.smoke-test.result }}" != "success" ]]; then + echo "One or more jobs failed." + exit 1 + fi + echo "All checks passed." diff --git a/.gitignore b/.gitignore index 52d2824e..f5aa195e 100644 --- a/.gitignore +++ b/.gitignore @@ -123,4 +123,5 @@ github-app/ **/i18n/compiled/ **/i18n/missing/ **/i18n/unused/ -tsup.config.bundled*.mjs \ No newline at end of file +tsup.config.bundled*.mjs +.playwright-mcp/ \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.cjs similarity index 60% rename from .prettierrc.js rename to .prettierrc.cjs index 4cb0c80e..bf46d885 100644 --- a/.prettierrc.js +++ b/.prettierrc.cjs @@ -1,3 +1,3 @@ const { prettierConfig } = require('@ttoss/config'); -module.exports = prettierConfig(); \ No newline at end of file +module.exports = prettierConfig(); diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..62337dfb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,61 @@ +FROM node:24-alpine AS builder + +WORKDIR /app + +# Install pnpm +RUN corepack enable && corepack prepare pnpm@latest --activate + +# Copy workspace manifests +COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./ +COPY packages/server/package.json ./packages/server/ +COPY packages/postgresdb/package.json ./packages/postgresdb/ +COPY packages/cli/package.json ./packages/cli/ + +# Use hoisted node_modules layout so devDep binaries (e.g. tsup) are resolvable +RUN echo "node-linker=hoisted" > .npmrc +RUN pnpm install --frozen-lockfile + +# Copy source code +COPY packages/server ./packages/server +COPY packages/postgresdb ./packages/postgresdb + +# Build postgresdb first (server depends on it) +RUN pnpm --filter @soat/postgresdb build + +# Build server +RUN pnpm --filter @soat/server build + +# ---- Production image ---- +FROM node:24-alpine + +WORKDIR /app + +RUN corepack enable && corepack prepare pnpm@latest --activate + +# Copy workspace manifests +COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./ +COPY packages/server/package.json ./packages/server/ +COPY packages/postgresdb/package.json ./packages/postgresdb/ +COPY packages/cli/package.json ./packages/cli/ + +# Install production dependencies only (skip lifecycle scripts like husky) +RUN pnpm install --frozen-lockfile --prod --ignore-scripts + +# Copy built artifacts +COPY --from=builder /app/packages/server/dist ./packages/server/dist +COPY --from=builder /app/packages/postgresdb/dist ./packages/postgresdb/dist + +# Mark dist/esm as ESM so Node.js parses import statements correctly +RUN echo '{"type":"module"}' > packages/server/dist/esm/package.json + +# Directory where uploaded files are persisted +ENV FILES_STORAGE_DIR=/data/files + +# Create the default storage directory +RUN mkdir -p /data/files + +VOLUME ["/data/files"] + +EXPOSE 5047 + +CMD ["node", "packages/server/dist/esm/server.js"] diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 00000000..22f0fd86 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,67 @@ +services: + database: + image: pgvector/pgvector:0.8.1-pg18-trixie + container_name: soat-database-dev-docker + environment: + POSTGRES_DB: ${DATABASE_NAME:-soat_dev} + POSTGRES_USER: ${DATABASE_USER:-soat_user} + POSTGRES_PASSWORD: ${DATABASE_PASSWORD:-soat_password} + volumes: + - postgres_data:/var/lib/postgresql + healthcheck: + test: + [ + 'CMD-SHELL', + 'pg_isready -U ${DATABASE_USER:-soat_user} -d ${DATABASE_NAME:-soat_dev}', + ] + interval: 10s + timeout: 5s + retries: 5 + + ollama: + image: ollama/ollama:latest + container_name: soat-ollama-dev-docker + logging: + driver: 'none' + volumes: + - ollama_cache:/root/.ollama + entrypoint: + - /bin/sh + - -c + - 'ollama serve > /dev/null 2>&1 & sleep 5 && ollama pull qwen3-embedding:0.6b > /dev/null 2>&1 && ollama pull qwen2.5:0.5b > /dev/null 2>&1 && wait' + healthcheck: + test: ['CMD-SHELL', 'ollama list | grep qwen3-embedding'] + interval: 10s + timeout: 30s + retries: 30 + start_period: 30s + + server: + build: . + container_name: soat-server-dev-docker + depends_on: + database: + condition: service_healthy + ollama: + condition: service_healthy + ports: + - '5047:5047' + environment: + DATABASE_HOST: database + DATABASE_PORT: '5432' + DATABASE_NAME: ${DATABASE_NAME:-soat_dev} + DATABASE_USER: ${DATABASE_USER:-soat_user} + DATABASE_PASSWORD: ${DATABASE_PASSWORD:-soat_password} + FILES_STORAGE_DIR: /data/files + OLLAMA_BASE_URL: http://ollama:11434 + EMBEDDING_PROVIDER: ollama + EMBEDDING_MODEL: qwen3-embedding:0.6b + EMBEDDING_DIMENSIONS: '1024' + AGENT_MODEL: qwen2.5:0.5b + volumes: + - files_data:/data/files + +volumes: + postgres_data: + ollama_cache: + files_data: diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 00000000..fba9ebf3 --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,83 @@ +services: + database: + image: pgvector/pgvector:0.8.1-pg18-trixie + environment: + POSTGRES_DB: soat_test + POSTGRES_USER: soat_user + POSTGRES_PASSWORD: soat_password + tmpfs: + - /var/lib/postgresql + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U soat_user -d soat_test'] + interval: 5s + timeout: 5s + retries: 10 + + ollama: + image: ollama/ollama:latest + volumes: + - ${HOME}/.ollama:/root/.ollama + entrypoint: + - /bin/sh + - -c + - 'ollama serve & sleep 5 && (ollama list | grep -q qwen3-embedding:0.6b || ollama pull qwen3-embedding:0.6b) && (ollama list | grep -q qwen2.5:0.5b || ollama pull qwen2.5:0.5b) && wait' + healthcheck: + test: + [ + 'CMD-SHELL', + 'ollama list | grep qwen3-embedding && ollama list | grep qwen2.5', + ] + interval: 10s + timeout: 30s + retries: 30 + start_period: 30s + + server: + build: + context: . + cache_from: + - type=gha + cache_to: + - type=gha,mode=max + depends_on: + database: + condition: service_healthy + ollama: + condition: service_healthy + environment: + PORT: 50477 + DATABASE_HOST: database + DATABASE_PORT: '5432' + DATABASE_NAME: soat_test + DATABASE_USER: soat_user + DATABASE_PASSWORD: soat_password + FILES_STORAGE_DIR: /data/files + OLLAMA_BASE_URL: http://ollama:11434 + EMBEDDING_PROVIDER: ollama + EMBEDDING_MODEL: qwen3-embedding:0.6b + EMBEDDING_DIMENSIONS: '1024' + AGENT_MODEL: qwen2.5:0.5b + healthcheck: + test: + - CMD + - node + - '-e' + - >- + require('http').get('http://localhost:50477/health', + (r) => { process.exit(r.statusCode === 200 ? 0 : 1); } + ).on('error', () => process.exit(1)) + interval: 5s + timeout: 10s + retries: 15 + start_period: 30s + + smoke-test: + image: alpine:latest + depends_on: + server: + condition: service_healthy + volumes: + - ./tests/smoke-test.sh:/smoke-test.sh:ro + environment: + SERVER_URL: 'http://server:50477' + command: sh -c "apk add --no-cache curl jq && sh /smoke-test.sh" diff --git a/docs/prd/chats-agents.md b/docs/prd/chats-agents.md new file mode 100644 index 00000000..f8d036fa --- /dev/null +++ b/docs/prd/chats-agents.md @@ -0,0 +1,365 @@ +# PRD: Secrets, AI Providers, Chats, and Agents + +## Context + +Soat acts as an **LLM Gateway**: applications call Soat instead of calling LLM providers directly. Soat adds auth, routing, logging, and cost tracking as middleware. Applications swap only `baseURL` and `apiKey` — no other client-side changes required. + +Four modules work together to deliver this: + +| Module | Role | +| ------------ | -------------------------------------------------- | +| Secrets | Store credentials (API keys, OAuth tokens, etc.) | +| AI Providers | Configure a provider + model using a secret | +| Chats | OpenAI-compatible request → response conversations | +| Agents | Multi-step tool-using workflows (ReAct loop) | + +Dependency chain: **Secrets ← AI Providers ← Chats / Agents** + +--- + +## Module: Secrets + +### Overview + +The Secrets module stores sensitive credentials scoped to a project. A secret can hold any kind of credential: an AI provider API key, Google service account JSON, OAuth refresh tokens, or arbitrary key-value pairs. Secrets are encrypted at rest and their values are never returned in API responses after creation. + +### Data Model + +- `Secret` — a named credential; scoped to a `Project` + - `publicId` — `sec_` prefix + - `projectId` — FK to `Project` + - `name` — human-readable label (e.g., "OpenAI Production Key") + - `value` — encrypted JSON string; can hold any structure (a plain string for API keys, a JSON object for Google credentials, etc.) + - `createdAt`, `updatedAt` + +### API + +``` +POST /v1/secrets Create a secret +GET /v1/secrets List secrets (scoped to project; values redacted) +GET /v1/secrets/{secretId} Get secret metadata (value redacted) +PATCH /v1/secrets/{secretId} Update a secret (name, type, or value) +DELETE /v1/secrets/{secretId} Delete a secret +``` + +### Behaviour + +- **Value is write-only**: `POST` and `PATCH` accept a `value` field; `GET` never returns it. List/Get responses include a `hasValue: true` flag instead. +- **Encryption**: values are encrypted with AES-256-GCM using a server-managed key (`SECRETS_ENCRYPTION_KEY` env var) before being stored in the database. +- **Cascade**: deleting a secret that is referenced by an AI provider returns `409 Conflict` unless `force=true` is passed (which also deletes dependent AI providers). + +### Public ID prefix + +`sec_` + +--- + +## Module: AI Providers + +### Overview + +The AI Providers module configures a connection to an AI model provider. Each AI provider references a secret (for credentials) and specifies a provider type and default model. A project can have multiple AI providers — e.g., two separate OpenAI providers with different API keys, plus an Anthropic provider. + +Chats and Agents reference an AI provider instead of specifying model/credentials directly. This decouples credential management from LLM usage. + +### Data Model + +- `AiProvider` — a configured provider instance; scoped to a `Project` + - `publicId` — `aip_` prefix + - `projectId` — FK to `Project` + - `secretId` — FK to `Secret` (the credential to authenticate with the provider) + - `name` — human-readable label (e.g., "Claude Production", "OpenAI Internal") + - `provider` — provider slug matching the Vercel AI SDK gateway format: `openai` | `anthropic` | `google` | `xai` | `groq` | `ollama` | `azure` | `bedrock` | `custom` + - `defaultModel` — default model ID (e.g., `gpt-4o`, `claude-sonnet-4`, `llama3.2`); can be overridden per-request + - `baseUrl` — optional; custom API base URL (for self-hosted or proxy endpoints) + - `config` — optional JSON; provider-specific settings (e.g., `{ "organization": "org-xxx" }` for OpenAI, `{ "region": "us-east-1" }` for Bedrock) + - `createdAt`, `updatedAt` + +### API + +``` +POST /v1/ai-providers Create an AI provider +GET /v1/ai-providers List AI providers (scoped to project) +GET /v1/ai-providers/{aiProviderId} Get AI provider details +PATCH /v1/ai-providers/{aiProviderId} Update an AI provider +DELETE /v1/ai-providers/{aiProviderId} Delete an AI provider +``` + +### Behaviour + +- **Secret resolution**: when Chats or Agents use an AI provider, the server decrypts the referenced secret to build the provider client. The decrypted value is never exposed to the caller. +- **Vercel AI SDK integration**: at runtime, each AI provider maps to a Vercel AI SDK provider instance. For example, an `openai` provider creates an `@ai-sdk/openai` instance configured with the decrypted API key and optional base URL. +- **Multiple providers per project**: a project may have several AI providers of the same type (e.g., two OpenAI providers for different teams/budgets) or different types. +- **Cascade**: deleting an AI provider that is referenced as a default by chats returns `409 Conflict` unless `force=true`. + +### Public ID prefix + +`aip_` + +### Provider → AI SDK Mapping + +| `provider` value | AI SDK package | Auth from secret | +| ---------------- | ------------------------- | --------------------- | +| `openai` | `@ai-sdk/openai` | `apiKey` | +| `anthropic` | `@ai-sdk/anthropic` | `apiKey` | +| `google` | `@ai-sdk/google` | `apiKey` | +| `xai` | `@ai-sdk/xai` | `apiKey` | +| `groq` | `@ai-sdk/groq` | `apiKey` | +| `azure` | `@ai-sdk/azure` | `apiKey` + `baseUrl` | +| `bedrock` | `@ai-sdk/amazon-bedrock` | `google_credentials` | +| `ollama` | `ollama-ai-provider` | none (local) | +| `gateway` | `ai` (built-in gateway) | `apiKey` (AI Gateway) | +| `custom` | OpenAI-compatible wrapper | `apiKey` + `baseUrl` | + +--- + +## Module: Chats + +### Overview + +The Chats module exposes an **OpenAI Chat Completions-compatible API** for **stateless, single-call completions**. Any client already using the OpenAI SDK can point its `baseURL` at Soat and work without code changes. + +Chats are **not** persistent — every request is self-contained. The caller sends the full message array each time. For persistent conversation history, use the **Conversations** module instead. An application can use Conversations without Chats, and vice versa. + +### Data Model + +Chats are stateless — no new DB entity is required. Each request is processed and returned without storing messages. + +### API + +``` +POST /v1/chats/completions Send messages and get a completion +``` + +#### `POST /v1/chats/completions` + +Request body mirrors OpenAI's `POST /v1/chat/completions`: + +```json +{ + "aiProviderId": "aip_V1StGXR8Z5jdHi6B", + "model": "gpt-4o", + "messages": [{ "role": "user", "content": "What files do I have?" }], + "stream": false +} +``` + +- `aiProviderId` — optional; specifies which AI provider to use for this request +- `model` — optional; overrides the AI provider's `defaultModel` +- `messages` — the full message array (system, user, assistant turns); the caller manages history +- `stream` — `false` returns JSON; `true` returns SSE (`text/event-stream`) + +**Resolution order for provider/model:** + +1. `aiProviderId` on the request body +2. Fallback: `CHAT_MODEL` env var with default Ollama (backward compat) + +Response (non-streaming) mirrors OpenAI's response shape: + +```json +{ + "object": "chat.completion", + "model": "gpt-4o", + "choices": [ + { + "index": 0, + "message": { "role": "assistant", "content": "You have 3 files: ..." }, + "finish_reason": "stop" + } + ] +} +``` + +Streaming response: SSE chunks with `data: {"choices":[{"delta":{"content":"..."}}]}` matching the OpenAI streaming format, terminated with `data: [DONE]`. + +### Behaviour + +- Each call is **stateless** — Soat does not store or accumulate messages. The caller is responsible for managing conversation history and sending the full message array on every request. +- No tool execution. If an application needs tools, it uses the Agents module. +- The AI provider determines which SDK client and credentials are used. The server decrypts the secret, instantiates the appropriate Vercel AI SDK provider, and calls `generateText` or `streamText`. + +### Relationship with Conversations + +Chats and Conversations are **independent modules**: + +| Aspect | Chats | Conversations | +| ----------- | -------------------------------- | ---------------------------------- | +| State | Stateless (single API call) | Persistent (messages stored in DB) | +| History | Caller manages message array | Soat manages message history | +| Use case | Direct LLM proxy / quick prompts | Multi-turn sessions with recall | +| Data stored | None | ConversationMessage per turn | + +An application can use either or both modules depending on its needs. + +--- + +## Module: Agents + +### Overview + +The Agents module executes **multi-step, tool-using workflows**. The caller provides a goal; the agent decides which tools to call and in what order until the goal is met or a step limit is reached. + +This is the ReAct loop pattern: Reason → Act → Observe → repeat. + +There is no OpenAI-compatible wrapper here — the API surface is Soat-native because no equivalent standard exists for agentic loops. + +### Data Model + +Agents do not require a persistent DB entity for most use cases (a run is ephemeral). Future iterations may persist run history, but v1 is stateless per run. + +If persistence is needed in the future: + +- `AgentRun` — one execution; has a `status` (`running` | `completed` | `failed`), a `goal`, and a reference to the project +- `AgentStep` — one ReAct iteration within a run; stores the model's reasoning, the tool called, and the tool result + +### API + +``` +POST /v1/agents/run Run an agent to completion (blocking, JSON response) +POST /v1/agents/run/stream Run an agent with streaming output (SSE) +``` + +#### `POST /v1/agents/run/stream` + +Already implemented (to be updated). Request: + +```json +{ + "aiProviderId": "aip_V1StGXR8Z5jdHi6B", + "model": "gpt-4o", + "prompt": "Summarise all documents in project proj_XYZ" +} +``` + +- `aiProviderId` — required (or fallback to `AGENT_MODEL` env var + Ollama for backward compat) +- `model` — optional; overrides the AI provider's `defaultModel` + +Response: SSE chunks: + +``` +data: {"text":"I'll look at the documents..."} +data: {"text":" Here is the summary:"} +data: {"event":"done"} +``` + +#### `POST /v1/agents/run` (future) + +Same request body; returns when the agent finishes: + +```json +{ + "runId": "run_V1StGXR8Z5jdHi6B", + "status": "completed", + "result": "...", + "steps": 4 +} +``` + +### Tools available to agents + +Tools are registered from the MCP tool definitions. Current tool surface: + +| Tool | Description | +| -------------------- | ----------------------- | +| `list-files` | List files in a project | +| `list-documents` | List documents | +| `list-conversations` | List conversations | +| `list-projects` | List projects | +| `list-actors` | List actors | + +Future tool additions (e.g. `create-document`, `search-documents`) extend the registry without changing the agent API. + +### Stop conditions (v1) + +- Default: maximum 20 steps +- Agent emits a `done` step with no tool call + +--- + +## Implementation Notes + +### SDK dependency + +Current state: `ollama@^0.6.3` (native). No tool loop. + +Migrate to **Vercel AI SDK**: install `ai` + provider-specific packages (`@ai-sdk/openai`, `@ai-sdk/anthropic`, etc.). This enables: + +- **Chats**: `generateText` / `streamText` for OpenAI-compatible completions +- **Agents**: `generateText` with `maxSteps` for the ReAct tool loop +- **AI Providers**: each `AiProvider` record maps to a Vercel AI SDK provider instance at runtime + +### Provider instantiation (runtime) + +```ts +// Pseudocode: resolving an AI provider to a Vercel AI SDK model +const resolveModel = async (aiProviderId: string) => { + const aiProvider = await db.AiProvider.findOne({ + where: { publicId: aiProviderId }, + }); + const secret = await db.Secret.findOne({ + where: { id: aiProvider.secretId }, + }); + const decryptedValue = decrypt(secret.value); + + switch (aiProvider.provider) { + case 'openai': + return createOpenAI({ + apiKey: decryptedValue, + baseURL: aiProvider.baseUrl, + })(aiProvider.defaultModel); + case 'anthropic': + return createAnthropic({ apiKey: decryptedValue })( + aiProvider.defaultModel + ); + case 'ollama': + return ollama(aiProvider.defaultModel); + // ... other providers + } +}; +``` + +### Encryption for Secrets + +- Algorithm: AES-256-GCM +- Key: `SECRETS_ENCRYPTION_KEY` environment variable (32 bytes, hex or base64 encoded) +- Each secret gets a random IV stored alongside the ciphertext +- Storage format: `iv:ciphertext:authTag` (all base64) + +### Module file locations (following codebase conventions) + +``` +packages/postgresdb/src/models/Secret.ts (new) +packages/postgresdb/src/models/AiProvider.ts (new) + +packages/server/src/lib/secrets.ts (new) +packages/server/src/lib/aiProviders.ts (new) +packages/server/src/lib/chats.ts (new) +packages/server/src/lib/agents.ts (exists, extend) + +packages/server/src/rest/v1/secrets.ts (new) +packages/server/src/rest/v1/aiProviders.ts (new) +packages/server/src/rest/v1/chats.ts (new) +packages/server/src/rest/v1/agents.ts (exists, extend) + +packages/server/src/mcp/tools/secrets.ts (new) +packages/server/src/mcp/tools/aiProviders.ts (new) +packages/server/src/mcp/tools/chats.ts (new) +packages/server/src/mcp/tools/agents.ts (new) + +packages/website/docs/modules/secrets.md (new) +packages/website/docs/modules/ai-providers.md (new) +packages/website/docs/modules/chats.md (new) +packages/website/docs/modules/agents.md (new) + +packages/server/tests/unit/tests/secrets.test.ts (new) +packages/server/tests/unit/tests/aiProviders.test.ts (new) +packages/server/tests/unit/tests/chats.test.ts (new) +packages/server/tests/unit/tests/agents.test.ts (new) +``` + +### Implementation order + +1. **Secrets** — no dependencies; foundation for everything else +2. **AI Providers** — depends on Secrets +3. **Chats** — depends on AI Providers; stateless, no new DB models needed +4. **Agents** — depends on AI Providers; extends existing agent streaming diff --git a/eslint.config.mjs b/eslint.config.mjs index 57cf2322..1e8e7865 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,3 +1,10 @@ import ttossEslintConfig from '@ttoss/eslint-config'; -export default [...ttossEslintConfig]; +export default [ + ...ttossEslintConfig, + { + rules: { + 'turbo/no-undeclared-env-vars': 'off', + }, + }, +]; diff --git a/package.json b/package.json index ed8a3cb9..966201a2 100644 --- a/package.json +++ b/package.json @@ -15,30 +15,34 @@ }, "scripts": { "prepare": "husky", - "syncpack:fix": "syncpack fix-mismatches", - "syncpack:list": "syncpack list-mismatches" + "smoke-test": "docker compose -f docker-compose.test.yml up --build --renew-anon-volumes --remove-orphans --abort-on-container-exit --exit-code-from smoke-test", + "postsmoke-test": "docker compose -f docker-compose.test.yml down --volumes", + "dev": "docker compose -f docker-compose.dev.yml up --build", + "syncpack:fix": "syncpack fix", + "syncpack:lint": "syncpack lint" }, "devDependencies": { - "@commitlint/cli": "^20.1.0", - "@lerna-lite/changed": "^4.9.4", - "@lerna-lite/cli": "^4.9.4", - "@lerna-lite/list": "^4.9.4", - "@lerna-lite/version": "^4.9.4", - "@ttoss/config": "^1.35.12", - "@ttoss/eslint-config": "^1.26.6", - "@ttoss/monorepo": "^1.28.0", - "@types/node": "^25.0.3", + "@commitlint/cli": "^20.5.0", + "@lerna-lite/changed": "^5.0.0", + "@lerna-lite/cli": "^5.0.0", + "@lerna-lite/list": "^5.0.0", + "@lerna-lite/version": "^5.0.0", + "@ttoss/config": "^1.37.8", + "@ttoss/eslint-config": "^1.26.14", + "@ttoss/monorepo": "^1.29.8", + "@types/node": "^25.5.2", + "carlin": "^1.48.3", "eslint": "^9.39.1", "husky": "^9.1.7", - "lint-staged": "^16.2.7", - "prettier": "^3.7.4", - "syncpack": "13.0.4", - "turbo": "^2.6.2", - "typescript": "~5.9.3" + "lint-staged": "^16.4.0", + "prettier": "^3.8.1", + "syncpack": "^14.3.0", + "turbo": "^2.9.4", + "typescript": "~6.0.2" }, "engines": { "node": "^24.0.0", "pnpm": "^10.0.0" }, - "packageManager": "pnpm@10.26.2" + "packageManager": "pnpm@10.33.0" } diff --git a/packages/cli/package.json b/packages/cli/package.json index 5a27d94a..943c7ec2 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -2,17 +2,16 @@ "name": "@soat/cli", "version": "0.0.0-alpha.2", "scripts": { - "build": "tsup", - "test": "jest --projects tests/unit" + "build": "tsup" }, "dependencies": { "@ttoss/logger": "^0.7.1", "commander": "^14.0.2" }, "devDependencies": { - "@ttoss/config": "^1.35.12", + "@ttoss/config": "^1.37.8", "@types/jest": "^30.0.0", - "jest": "^30.2.0", + "jest": "^30.3.0", "tsup": "^8.5.1", "tsx": "^4.21.0" }, diff --git a/packages/documents-core/CHANGELOG.md b/packages/documents-core/CHANGELOG.md deleted file mode 100644 index 9b8c4e76..00000000 --- a/packages/documents-core/CHANGELOG.md +++ /dev/null @@ -1,14 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -# 0.0.0-alpha.2 (2026-01-06) - -### Bug Fixes - -* add version ([de8fab4](https://github.com/ttoss/soat/commit/de8fab4e0d51ba0e06e0b29f9b26ea8d147d92a6)) - -### Features - -* database working ([5a5d34d](https://github.com/ttoss/soat/commit/5a5d34d5820c0279b14f3a135b9a55f728cf8f65)) diff --git a/packages/documents-core/package.json b/packages/documents-core/package.json deleted file mode 100644 index ddb9345e..00000000 --- a/packages/documents-core/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "@soat/documents-core", - "version": "0.0.0-alpha.2", - "description": "Core package for saving and managing documents with local, S3, and GCS storage", - "type": "module", - "exports": "./src/index.ts", - "types": "dist/index.d.ts", - "files": [ - "dist" - ], - "publishConfig": { - "access": "public" - }, - "scripts": { - "build": "tsup", - "dev": "tsup --watch", - "test": "jest" - }, - "dependencies": { - "@soat/embeddings-core": "workspace:*", - "@soat/files-core": "workspace:*", - "@soat/postgresdb": "workspace:*", - "@ttoss/postgresdb": "^0.3.0", - "uuid": "^10.0.0" - }, - "devDependencies": { - "@ttoss/config": "^1.35.12", - "@types/jest": "^30.0.0", - "@types/node": "^25.0.3", - "@types/uuid": "^10.0.0", - "jest": "^30.2.0", - "tsup": "^8.5.1", - "tsx": "^4.21.0" - } -} diff --git a/packages/documents-core/src/database.ts b/packages/documents-core/src/database.ts deleted file mode 100644 index 7e64ccd9..00000000 --- a/packages/documents-core/src/database.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { models } from '@soat/postgresdb'; - -import type { DocumentRecord } from './types'; - -const parseMetadata = ( - metadata?: string -): Record | undefined => { - if (!metadata) return undefined; - try { - return JSON.parse(metadata); - } catch { - return undefined; - } -}; - -const toDocumentRecord = ( - doc: InstanceType -): DocumentRecord => { - return { - id: doc.id, - title: doc.title, - fileId: doc.fileId, - embeddingModel: doc.embeddingModel, - embeddingProvider: doc.embeddingProvider, - embedding: doc.embedding, - metadata: parseMetadata(doc.metadata), - createdAt: doc.createdAt, - updatedAt: doc.updatedAt, - }; -}; - -export const saveDocumentRecord = async (args: { - id: string; - title?: string; - fileId: string; - embeddingModel?: string; - embeddingProvider?: string; - embedding?: number[]; - metadata?: Record; -}): Promise => { - const doc = await models.Document.create({ - ...args, - metadata: args.metadata ? JSON.stringify(args.metadata) : undefined, - } as Parameters[0]); - - return toDocumentRecord(doc); -}; - -export const getDocumentRecord = async ( - id: string -): Promise => { - const doc = await models.Document.findByPk(id); - if (!doc) return null; - return toDocumentRecord(doc); -}; - -export const updateDocumentRecord = async ( - id: string, - updates: Partial< - Pick< - DocumentRecord, - | 'title' - | 'embeddingModel' - | 'embeddingProvider' - | 'embedding' - | 'metadata' - > - > -): Promise => { - const doc = await models.Document.findByPk(id); - if (!doc) return null; - - const updateData: Partial<{ - title: string; - embeddingModel: string; - embeddingProvider: string; - embedding: number[]; - metadata: string | null; - }> = {}; - if (updates.title !== undefined) updateData.title = updates.title; - if (updates.embeddingModel !== undefined) - updateData.embeddingModel = updates.embeddingModel; - if (updates.embeddingProvider !== undefined) - updateData.embeddingProvider = updates.embeddingProvider; - if (updates.embedding !== undefined) updateData.embedding = updates.embedding; - if (updates.metadata !== undefined) { - updateData.metadata = updates.metadata - ? JSON.stringify(updates.metadata) - : null; - } - - await doc.update(updateData); - return getDocumentRecord(id); -}; - -export const deleteDocumentRecord = async (id: string): Promise => { - const doc = await models.Document.findByPk(id); - if (!doc) return false; - await doc.destroy(); - return true; -}; - -export const listDocumentRecords = async (): Promise => { - const docs = await models.Document.findAll(); - return docs.map(toDocumentRecord); -}; - -export const getDocumentRecordByFileId = async ( - fileId: string -): Promise => { - const doc = await models.Document.findOne({ where: { fileId } }); - if (!doc) return null; - return toDocumentRecord(doc); -}; diff --git a/packages/documents-core/src/documents.ts b/packages/documents-core/src/documents.ts deleted file mode 100644 index 19d9fa88..00000000 --- a/packages/documents-core/src/documents.ts +++ /dev/null @@ -1,317 +0,0 @@ -import { generateEmbedding, type EmbeddingConfig } from '@soat/embeddings-core'; -import { - deleteFile, - retrieveFileById, - saveFile, - type StorageConfig, -} from '@soat/files-core'; -import { v4 as uuidv4 } from 'uuid'; - -import { - deleteDocumentRecord, - getDocumentRecord, - getDocumentRecordByFileId, - listDocumentRecords, - saveDocumentRecord, - updateDocumentRecord, -} from './database'; -import type { - CreateDocumentOptions, - Document, - DocumentRecord, - SearchDocumentsOptions, -} from './types'; - -export const createDocument = async (args: { - storageConfig: StorageConfig; - embeddingConfig?: EmbeddingConfig; - content: string; - options?: CreateDocumentOptions; -}): Promise => { - const { storageConfig, embeddingConfig, content, options } = args; - const id = uuidv4(); - - // Save content as markdown file - const file = await saveFile({ - config: storageConfig, - content, - options: { - contentType: 'text/markdown', - metadata: { - filename: `${id}.md`, - documentId: id, - ...options?.metadata, - }, - }, - }); - - let embeddingResult: - | { embedding: number[]; model: string; provider: string } - | undefined; - - // Generate embedding if config is provided and option is enabled (default: true) - if (embeddingConfig && options?.generateEmbedding !== false) { - const result = await generateEmbedding({ - config: embeddingConfig, - text: content, - }); - embeddingResult = { - embedding: result.embedding, - model: result.model, - provider: result.provider, - }; - } - - // Save document record - const record = await saveDocumentRecord({ - id, - title: options?.title, - fileId: file.id, - embeddingModel: embeddingResult?.model, - embeddingProvider: embeddingResult?.provider, - embedding: embeddingResult?.embedding, - metadata: options?.metadata, - }); - - return { - id: record.id, - title: record.title, - fileId: record.fileId, - content, - embeddingModel: record.embeddingModel, - embeddingProvider: record.embeddingProvider, - embedding: record.embedding, - metadata: record.metadata, - createdAt: record.createdAt, - updatedAt: record.updatedAt, - }; -}; - -export const getDocument = async (args: { - storageConfig: StorageConfig; - id: string; -}): Promise => { - const { storageConfig, id } = args; - - const record = await getDocumentRecord(id); - if (!record) return null; - - const file = await retrieveFileById({ - config: storageConfig, - id: record.fileId, - }); - - return { - id: record.id, - title: record.title, - fileId: record.fileId, - content: file?.content, - embeddingModel: record.embeddingModel, - embeddingProvider: record.embeddingProvider, - embedding: record.embedding, - metadata: record.metadata, - createdAt: record.createdAt, - updatedAt: record.updatedAt, - }; -}; - -export const updateDocument = async (args: { - storageConfig: StorageConfig; - embeddingConfig?: EmbeddingConfig; - id: string; - content?: string; - title?: string; - metadata?: Record; - regenerateEmbedding?: boolean; -}): Promise => { - const { - storageConfig, - embeddingConfig, - id, - content, - title, - metadata, - regenerateEmbedding, - } = args; - - const record = await getDocumentRecord(id); - if (!record) return null; - - let newFileId = record.fileId; - let embeddingResult: - | { embedding: number[]; model: string; provider: string } - | undefined; - - // Update content if provided - if (content !== undefined) { - // Delete old file - await deleteFile({ config: storageConfig, id: record.fileId }); - - // Save new file - const newFile = await saveFile({ - config: storageConfig, - content, - options: { - contentType: 'text/markdown', - metadata: { - filename: `${id}.md`, - documentId: id, - ...metadata, - }, - }, - }); - newFileId = newFile.id; - - // Regenerate embedding if config is provided - if (embeddingConfig && regenerateEmbedding !== false) { - const result = await generateEmbedding({ - config: embeddingConfig, - text: content, - }); - embeddingResult = { - embedding: result.embedding, - model: result.model, - provider: result.provider, - }; - } - } - - // Update document record - const updatedRecord = await updateDocumentRecord(id, { - title, - embeddingModel: embeddingResult?.model, - embeddingProvider: embeddingResult?.provider, - embedding: embeddingResult?.embedding, - metadata, - }); - - if (!updatedRecord) return null; - - // Get updated content - const file = await retrieveFileById({ - config: storageConfig, - id: newFileId, - }); - - return { - id: updatedRecord.id, - title: updatedRecord.title, - fileId: newFileId, - content: file?.content, - embeddingModel: updatedRecord.embeddingModel, - embeddingProvider: updatedRecord.embeddingProvider, - embedding: updatedRecord.embedding, - metadata: updatedRecord.metadata, - createdAt: updatedRecord.createdAt, - updatedAt: updatedRecord.updatedAt, - }; -}; - -export const deleteDocument = async (args: { - storageConfig: StorageConfig; - id: string; -}): Promise => { - const { storageConfig, id } = args; - - const record = await getDocumentRecord(id); - if (!record) return false; - - // Delete file - await deleteFile({ config: storageConfig, id: record.fileId }); - - // Delete document record - return deleteDocumentRecord(id); -}; - -export const listDocuments = async (): Promise => { - return listDocumentRecords(); -}; - -export const searchDocumentsBySimilarity = async (args: { - storageConfig: StorageConfig; - embeddingConfig: EmbeddingConfig; - query: string; - options?: SearchDocumentsOptions; -}): Promise => { - const { storageConfig, embeddingConfig, query, options } = args; - const limit = options?.limit ?? 10; - - // Generate embedding for query - const queryEmbedding = await generateEmbedding({ - config: embeddingConfig, - text: query, - }); - - // Get all documents with embeddings - const records = await listDocumentRecords(); - const documentsWithEmbeddings = records.filter( - (r) => r.embedding && r.embedding.length > 0 - ); - - // Calculate cosine similarity - const similarities = documentsWithEmbeddings.map((record) => { - const similarity = cosineSimilarity( - queryEmbedding.embedding, - record.embedding! - ); - return { record, similarity }; - }); - - // Sort by similarity and take top results - similarities.sort((a, b) => b.similarity - a.similarity); - const topResults = similarities.slice(0, limit); - - // Filter by threshold if provided - const filteredResults = options?.threshold - ? topResults.filter((r) => r.similarity >= options.threshold!) - : topResults; - - // Fetch content for each document - const documents: Document[] = []; - for (const { record } of filteredResults) { - const file = await retrieveFileById({ - config: storageConfig, - id: record.fileId, - }); - - documents.push({ - id: record.id, - title: record.title, - fileId: record.fileId, - content: file?.content, - embeddingModel: record.embeddingModel, - embeddingProvider: record.embeddingProvider, - embedding: record.embedding, - metadata: record.metadata, - createdAt: record.createdAt, - updatedAt: record.updatedAt, - }); - } - - return documents; -}; - -const cosineSimilarity = (a: number[], b: number[]): number => { - if (a.length !== b.length) { - throw new Error('Vectors must have the same length'); - } - - let dotProduct = 0; - let normA = 0; - let normB = 0; - - for (let i = 0; i < a.length; i++) { - dotProduct += a[i] * b[i]; - normA += a[i] * a[i]; - normB += b[i] * b[i]; - } - - normA = Math.sqrt(normA); - normB = Math.sqrt(normB); - - if (normA === 0 || normB === 0) { - return 0; - } - - return dotProduct / (normA * normB); -}; diff --git a/packages/documents-core/src/index.ts b/packages/documents-core/src/index.ts deleted file mode 100644 index a72ed738..00000000 --- a/packages/documents-core/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './database'; -export * from './documents'; -export * from './types'; diff --git a/packages/documents-core/src/types.ts b/packages/documents-core/src/types.ts deleted file mode 100644 index 26a3ef23..00000000 --- a/packages/documents-core/src/types.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { EmbeddingConfig } from '@soat/embeddings-core'; -import type { StorageConfig, UploadOptions } from '@soat/files-core'; - -export interface Document { - id: string; - title?: string; - fileId: string; - content?: string | Buffer; - embeddingModel?: string; - embeddingProvider?: string; - embedding?: number[]; - metadata?: Record; - createdAt?: Date; - updatedAt?: Date; -} - -export interface DocumentRecord { - id: string; - title?: string; - fileId: string; - embeddingModel?: string; - embeddingProvider?: string; - embedding?: number[]; - metadata?: Record; - createdAt: Date; - updatedAt: Date; -} - -export interface CreateDocumentOptions { - title?: string; - metadata?: Record; - generateEmbedding?: boolean; -} - -export interface SearchDocumentsOptions { - limit?: number; - threshold?: number; -} - -export type { EmbeddingConfig, StorageConfig, UploadOptions }; diff --git a/packages/documents-core/tsconfig.json b/packages/documents-core/tsconfig.json deleted file mode 100644 index fce4d054..00000000 --- a/packages/documents-core/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "@ttoss/config/tsconfig.json" -} diff --git a/packages/documents-core/tsup.config.ts b/packages/documents-core/tsup.config.ts deleted file mode 100644 index 8db39855..00000000 --- a/packages/documents-core/tsup.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { tsupConfig } from '@ttoss/config'; - -export default tsupConfig(); diff --git a/packages/embeddings-core/CHANGELOG.md b/packages/embeddings-core/CHANGELOG.md deleted file mode 100644 index 0c39e82d..00000000 --- a/packages/embeddings-core/CHANGELOG.md +++ /dev/null @@ -1,10 +0,0 @@ -# @soat/embeddings-core - -## 0.0.0-alpha.1 - -### Features - -- Initial implementation of embeddings-core package -- Support for Ollama embedding provider -- Support for OpenAI embedding provider -- Environment-based configuration with `EMBEDDINGS_OLLAMA_MODEL` and `EMBEDDINGS_OPENAI_KEY` diff --git a/packages/embeddings-core/package.json b/packages/embeddings-core/package.json deleted file mode 100644 index fe679a67..00000000 --- a/packages/embeddings-core/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "@soat/embeddings-core", - "version": "0.0.0-alpha.1", - "description": "Core package for generating embeddings with Ollama and other providers", - "type": "module", - "exports": "./src/index.ts", - "types": "dist/index.d.ts", - "scripts": { - "build": "tsup", - "dev": "tsup --watch", - "test": "jest" - }, - "dependencies": { - "ollama": "^0.6.3" - }, - "devDependencies": { - "@ttoss/config": "^1.35.12", - "@types/node": "^25.0.3", - "jest": "^30.2.0", - "tsup": "^8.5.1", - "typescript": "~5.9.3" - } -} diff --git a/packages/embeddings-core/src/embeddings.ts b/packages/embeddings-core/src/embeddings.ts deleted file mode 100644 index becb89b2..00000000 --- a/packages/embeddings-core/src/embeddings.ts +++ /dev/null @@ -1,111 +0,0 @@ -import * as ollamaProvider from './providers/ollama.js'; -import * as openaiProvider from './providers/openai.js'; -import type { - EmbeddingConfig, - EmbeddingResult, - OllamaConfig, - OpenAIConfig, -} from './types.js'; - -const getProvider = (args: { config: EmbeddingConfig }) => { - const { config } = args; - switch (config.provider) { - case 'ollama': - return ollamaProvider; - case 'openai': - return openaiProvider; - default: - throw new Error(`Unsupported embedding provider: ${config.provider}`); - } -}; - -const getProviderConfig = (args: { config: EmbeddingConfig }) => { - const { config } = args; - switch (config.provider) { - case 'ollama': - if (!config.ollama) { - throw new Error('Ollama config is required for ollama provider'); - } - return config.ollama; - case 'openai': - if (!config.openai) { - throw new Error('OpenAI config is required for openai provider'); - } - return config.openai; - default: - throw new Error(`Unsupported embedding provider: ${config.provider}`); - } -}; - -export const generateEmbedding = async (args: { - config: EmbeddingConfig; - text: string; -}): Promise => { - const { config, text } = args; - const provider = getProvider({ config }); - const providerConfig = getProviderConfig({ config }); - - return provider.generateEmbedding({ - config: providerConfig as OllamaConfig & OpenAIConfig, - text, - }); -}; - -export const generateEmbeddings = async (args: { - config: EmbeddingConfig; - texts: string[]; -}): Promise => { - const { config, texts } = args; - const provider = getProvider({ config }); - const providerConfig = getProviderConfig({ config }); - - return provider.generateEmbeddings({ - config: providerConfig as OllamaConfig & OpenAIConfig, - texts, - }); -}; - -/** - * Creates an embedding config from environment variables. - * Supports: - * - EMBEDDINGS_OLLAMA_MODEL: Ollama model name (uses Ollama provider) - * - EMBEDDINGS_OLLAMA_HOST: Optional Ollama host URL - * - EMBEDDINGS_OPENAI_KEY: OpenAI API key (uses OpenAI provider) - * - EMBEDDINGS_OPENAI_MODEL: Optional OpenAI model name - * - * Priority: Ollama > OpenAI (if multiple are configured) - */ -export const getConfigFromEnv = (): EmbeddingConfig => { - // eslint-disable-next-line turbo/no-undeclared-env-vars - const ollamaModel = process.env.EMBEDDINGS_OLLAMA_MODEL; - // eslint-disable-next-line turbo/no-undeclared-env-vars - const ollamaHost = process.env.EMBEDDINGS_OLLAMA_HOST; - // eslint-disable-next-line turbo/no-undeclared-env-vars - const openaiKey = process.env.EMBEDDINGS_OPENAI_KEY; - // eslint-disable-next-line turbo/no-undeclared-env-vars - const openaiModel = process.env.EMBEDDINGS_OPENAI_MODEL; - - if (ollamaModel) { - return { - provider: 'ollama', - ollama: { - model: ollamaModel, - host: ollamaHost, - }, - }; - } - - if (openaiKey) { - return { - provider: 'openai', - openai: { - apiKey: openaiKey, - model: openaiModel, - }, - }; - } - - throw new Error( - 'No embedding provider configured. Set EMBEDDINGS_OLLAMA_MODEL or EMBEDDINGS_OPENAI_KEY' - ); -}; diff --git a/packages/embeddings-core/src/index.ts b/packages/embeddings-core/src/index.ts deleted file mode 100644 index 179a1287..00000000 --- a/packages/embeddings-core/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './embeddings'; -export * from './types'; diff --git a/packages/embeddings-core/src/providers/index.ts b/packages/embeddings-core/src/providers/index.ts deleted file mode 100644 index 514346ac..00000000 --- a/packages/embeddings-core/src/providers/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * as ollama from './ollama'; -export * as openai from './openai'; diff --git a/packages/embeddings-core/src/providers/ollama.ts b/packages/embeddings-core/src/providers/ollama.ts deleted file mode 100644 index e2c16701..00000000 --- a/packages/embeddings-core/src/providers/ollama.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Ollama } from 'ollama'; - -import type { EmbeddingResult, OllamaConfig } from '../types.js'; - -export const generateEmbedding = async (args: { - config: OllamaConfig; - text: string; -}): Promise => { - const { config, text } = args; - const ollama = new Ollama({ host: config.host }); - - const response = await ollama.embed({ - model: config.model, - input: text, - }); - - return { - embedding: response.embeddings[0], - model: config.model, - provider: 'ollama', - }; -}; - -export const generateEmbeddings = async (args: { - config: OllamaConfig; - texts: string[]; -}): Promise => { - const { config, texts } = args; - const ollama = new Ollama({ host: config.host }); - - const response = await ollama.embed({ - model: config.model, - input: texts, - }); - - return response.embeddings.map((embedding) => { - return { - embedding, - model: config.model, - provider: 'ollama' as const, - }; - }); -}; diff --git a/packages/embeddings-core/src/providers/openai.ts b/packages/embeddings-core/src/providers/openai.ts deleted file mode 100644 index 57ceef3f..00000000 --- a/packages/embeddings-core/src/providers/openai.ts +++ /dev/null @@ -1,71 +0,0 @@ -import type { EmbeddingResult, OpenAIConfig } from '../types.js'; - -const DEFAULT_MODEL = 'text-embedding-3-small'; - -export const generateEmbedding = async (args: { - config: OpenAIConfig; - text: string; -}): Promise => { - const { config, text } = args; - const model = config.model || DEFAULT_MODEL; - - const response = await fetch('https://api.openai.com/v1/embeddings', { - method: 'POST', - headers: { - Authorization: `Bearer ${config.apiKey}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - model, - input: text, - }), - }); - - if (!response.ok) { - const error = await response.text(); - throw new Error(`OpenAI API error: ${error}`); - } - - const data = await response.json(); - - return { - embedding: data.data[0].embedding, - model, - provider: 'openai', - }; -}; - -export const generateEmbeddings = async (args: { - config: OpenAIConfig; - texts: string[]; -}): Promise => { - const { config, texts } = args; - const model = config.model || DEFAULT_MODEL; - - const response = await fetch('https://api.openai.com/v1/embeddings', { - method: 'POST', - headers: { - Authorization: `Bearer ${config.apiKey}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - model, - input: texts, - }), - }); - - if (!response.ok) { - const error = await response.text(); - throw new Error(`OpenAI API error: ${error}`); - } - - const data = await response.json(); - - return data.data.map((item: { embedding: number[] }) => { - return { - embedding: item.embedding, - model, - provider: 'openai' as const, - }; - }); -}; diff --git a/packages/embeddings-core/src/types.ts b/packages/embeddings-core/src/types.ts deleted file mode 100644 index 5a9a8c18..00000000 --- a/packages/embeddings-core/src/types.ts +++ /dev/null @@ -1,23 +0,0 @@ -export type EmbeddingProvider = 'ollama' | 'openai'; - -export interface OllamaConfig { - model: string; - host?: string; -} - -export interface OpenAIConfig { - apiKey: string; - model?: string; -} - -export interface EmbeddingConfig { - provider: EmbeddingProvider; - ollama?: OllamaConfig; - openai?: OpenAIConfig; -} - -export interface EmbeddingResult { - embedding: number[]; - model: string; - provider: EmbeddingProvider; -} diff --git a/packages/embeddings-core/tsconfig.json b/packages/embeddings-core/tsconfig.json deleted file mode 100644 index abc66b3b..00000000 --- a/packages/embeddings-core/tsconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "isolatedModules": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/embeddings-core/tsup.config.ts b/packages/embeddings-core/tsup.config.ts deleted file mode 100644 index b2b5fef7..00000000 --- a/packages/embeddings-core/tsup.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { tsupConfig } from '@ttoss/config'; - -export default tsupConfig({ - entryPoints: ['src/index.ts'], -}); diff --git a/packages/files-core/CHANGELOG.md b/packages/files-core/CHANGELOG.md deleted file mode 100644 index d1cd0504..00000000 --- a/packages/files-core/CHANGELOG.md +++ /dev/null @@ -1,15 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -# 0.0.0-alpha.2 (2026-01-06) - -### Bug Fixes - -* add version ([de8fab4](https://github.com/ttoss/soat/commit/de8fab4e0d51ba0e06e0b29f9b26ea8d147d92a6)) - -### Features - -* database working ([5a5d34d](https://github.com/ttoss/soat/commit/5a5d34d5820c0279b14f3a135b9a55f728cf8f65)) -* files rest api ([957c8b0](https://github.com/ttoss/soat/commit/957c8b0aa2b5a1b96dd3da789be2552e7bf34599)) diff --git a/packages/files-core/package.json b/packages/files-core/package.json deleted file mode 100644 index afd5f345..00000000 --- a/packages/files-core/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "@soat/files-core", - "version": "0.0.0-alpha.2", - "description": "Core package for saving and managing files with local, S3, and GCS storage", - "exports": "./src/index.ts", - "scripts": { - "build": "tsup", - "dev": "tsup --watch", - "test": "jest" - }, - "dependencies": { - "@google-cloud/storage": "^7.0.0", - "@soat/postgresdb": "workspace:*", - "@ttoss/postgresdb": "^0.3.0", - "aws-sdk": "^2.0.0", - "uuid": "^10.0.0" - }, - "devDependencies": { - "@ttoss/config": "^1.35.12", - "@types/node": "^25.0.3", - "@types/uuid": "^10.0.0", - "jest": "^30.2.0", - "tsup": "^8.5.1", - "typescript": "~5.9.3" - } -} diff --git a/packages/files-core/src/database.ts b/packages/files-core/src/database.ts deleted file mode 100644 index 00db128b..00000000 --- a/packages/files-core/src/database.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { models } from './db'; -import type { FileRecord, StorageConfig, UploadOptions } from './types'; - -export const saveFileRecord = async (args: { - id: string; - filename?: string; - contentType?: string; - size?: number; - storageType: StorageConfig['type']; - storagePath: string; - metadata?: Record; -}): Promise => { - const file = await models.File.create({ - ...args, - metadata: args.metadata ? JSON.stringify(args.metadata) : undefined, - } as any); - - return { - id: file.id, - filename: file.filename, - contentType: file.contentType, - size: file.size, - storageType: file.storageType, - storagePath: file.storagePath, - metadata: file.metadata ? JSON.parse(file.metadata) : undefined, - createdAt: file.createdAt, - updatedAt: file.updatedAt, - }; -}; - -export const getFileRecord = async (id: string): Promise => { - const file = await models.File.findByPk(id); - - if (!file) { - return null; - } - - return { - id: file.id, - filename: file.filename, - contentType: file.contentType, - size: file.size, - storageType: file.storageType, - storagePath: file.storagePath, - metadata: file.metadata ? JSON.parse(file.metadata) : undefined, - createdAt: file.createdAt, - updatedAt: file.updatedAt, - }; -}; - -export const updateFileRecord = async ( - id: string, - updates: Partial< - Pick - > -): Promise => { - const file = await models.File.findByPk(id); - - if (!file) { - return null; - } - - const updateData: any = {}; - if (updates.filename !== undefined) updateData.filename = updates.filename; - if (updates.contentType !== undefined) - updateData.contentType = updates.contentType; - if (updates.size !== undefined) updateData.size = updates.size; - if (updates.metadata !== undefined) { - updateData.metadata = updates.metadata - ? JSON.stringify(updates.metadata) - : null; - } - - await file.update(updateData); - - return getFileRecord(id); -}; - -export const deleteFileRecord = async (id: string): Promise => { - const file = await models.File.findByPk(id); - - if (!file) { - return false; - } - - await file.destroy(); - return true; -}; - -export const listFileRecords = async (): Promise => { - const files = await models.File.findAll(); - - return files.map((file) => { - return { - id: file.id, - filename: file.filename, - contentType: file.contentType, - size: file.size, - storageType: file.storageType, - storagePath: file.storagePath, - metadata: file.metadata ? JSON.parse(file.metadata) : undefined, - createdAt: file.createdAt, - updatedAt: file.updatedAt, - }; - }); -}; diff --git a/packages/files-core/src/db.ts b/packages/files-core/src/db.ts deleted file mode 100644 index 572ad4d2..00000000 --- a/packages/files-core/src/db.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { models } from '@soat/postgresdb'; -import { initialize } from '@ttoss/postgresdb'; - -export const initializeDatabase = async () => { - return initialize({ models }); -}; - -export { models }; diff --git a/packages/files-core/src/files.ts b/packages/files-core/src/files.ts deleted file mode 100644 index fc021899..00000000 --- a/packages/files-core/src/files.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { promises as fs } from 'node:fs'; - -import { v4 as uuidv4 } from 'uuid'; - -import { deleteFileRecord, getFileRecord, saveFileRecord } from './database'; -import * as gcs from './storage/gcs'; -import * as local from './storage/local'; -import * as s3 from './storage/s3'; -import type { FileData, StorageConfig, UploadOptions } from './types'; - -const getStorage = (args: { config: StorageConfig }) => { - const { config } = args; - switch (config.type) { - case 'local': - return local; - case 's3': - return s3; - case 'gcs': - return gcs; - default: - throw new Error(`Unsupported storage type: ${config.type}`); - } -}; - -export const saveFile = async (args: { - config: StorageConfig; - content: string | Buffer; - options?: UploadOptions; -}): Promise => { - const { config, content, options } = args; - const id = uuidv4(); - const storage = getStorage({ config }); - await storage.save({ id, content, config }); - - // Save file record to database - await saveFileRecord({ - id, - contentType: options?.contentType, - size: Buffer.isBuffer(content) - ? content.length - : Buffer.byteLength(content), - storageType: config.type, - storagePath: id, - metadata: options?.metadata, - }); - - return { id, content }; -}; - -export const uploadFile = async (args: { - config: StorageConfig; - filePath: string; - options?: UploadOptions; -}): Promise => { - const { config, filePath, options } = args; - const content = await fs.readFile(filePath); - const filename = - (options?.metadata?.filename as string) || filePath.split('/').pop(); - return saveFile({ - config, - content, - options: { ...options, metadata: { ...options?.metadata, filename } }, - }); -}; - -export const retrieveFileById = async (args: { - config: StorageConfig; - id: string; -}): Promise => { - const { config, id } = args; - try { - const storage = getStorage({ config }); - const content = await storage.retrieve({ id, config }); - return { id, content }; - } catch { - return null; - } -}; - -export const deleteFile = async (args: { - config: StorageConfig; - id: string; -}): Promise => { - const { config, id } = args; - - // Delete from storage - const storage = getStorage({ config }); - await storage.deleteFile({ id, config }); - - // Delete from database - return deleteFileRecord(id); -}; diff --git a/packages/files-core/src/index.ts b/packages/files-core/src/index.ts deleted file mode 100644 index 906c8d20..00000000 --- a/packages/files-core/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './database'; -export * from './db'; -export * from './files'; -export * from './types'; diff --git a/packages/files-core/src/storage/gcs.ts b/packages/files-core/src/storage/gcs.ts deleted file mode 100644 index db6cc7d0..00000000 --- a/packages/files-core/src/storage/gcs.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Storage } from '@google-cloud/storage'; - -import type { StorageConfig } from '../types'; - -export const save = async (args: { - id: string; - content: string | Buffer; - config: StorageConfig; -}): Promise => { - const { id, content, config } = args; - if (!config.gcs) { - throw new Error('GCS config not provided'); - } - const storage = new Storage({ - keyFilename: config.gcs.keyFilename, - projectId: config.gcs.projectId, - }); - const bucket = storage.bucket(config.gcs.bucket); - const file = bucket.file(id); - await file.save(content); -}; - -export const retrieve = async (args: { - id: string; - config: StorageConfig; -}): Promise => { - const { id, config } = args; - if (!config.gcs) { - throw new Error('GCS config not provided'); - } - const storage = new Storage({ - keyFilename: config.gcs.keyFilename, - projectId: config.gcs.projectId, - }); - const bucket = storage.bucket(config.gcs.bucket); - const file = bucket.file(id); - const [buffer] = await file.download(); - return buffer; -}; - -export const deleteFile = async (args: { - id: string; - config: StorageConfig; -}): Promise => { - const { id, config } = args; - if (!config.gcs) { - throw new Error('GCS config not provided'); - } - const storage = new Storage({ - keyFilename: config.gcs.keyFilename, - projectId: config.gcs.projectId, - }); - const bucket = storage.bucket(config.gcs.bucket); - const file = bucket.file(id); - await file.delete(); -}; diff --git a/packages/files-core/src/storage/index.ts b/packages/files-core/src/storage/index.ts deleted file mode 100644 index 90a80466..00000000 --- a/packages/files-core/src/storage/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './local'; -export * from './s3'; -export * from './gcs'; diff --git a/packages/files-core/src/storage/local.ts b/packages/files-core/src/storage/local.ts deleted file mode 100644 index 0c0ddcc6..00000000 --- a/packages/files-core/src/storage/local.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { promises as fs } from 'node:fs'; -import path from 'node:path'; - -import type { StorageConfig } from '../types'; - -export const save = async (args: { - id: string; - content: string | Buffer; - config: StorageConfig; -}): Promise => { - const { id, content, config } = args; - if (!config.local) { - throw new Error('Local config not provided'); - } - const filePath = path.join(config.local.path, id); - // Ensure directory exists - await fs.mkdir(config.local.path, { recursive: true }); - await fs.writeFile(filePath, content); -}; - -export const retrieve = async (args: { - id: string; - config: StorageConfig; -}): Promise => { - const { id, config } = args; - if (!config.local) { - throw new Error('Local config not provided'); - } - const filePath = path.join(config.local.path, id); - return fs.readFile(filePath); -}; - -export const deleteFile = async (args: { - id: string; - config: StorageConfig; -}): Promise => { - const { id, config } = args; - if (!config.local) { - throw new Error('Local config not provided'); - } - const filePath = path.join(config.local.path, id); - await fs.unlink(filePath); -}; diff --git a/packages/files-core/src/storage/s3.ts b/packages/files-core/src/storage/s3.ts deleted file mode 100644 index d5aab773..00000000 --- a/packages/files-core/src/storage/s3.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { S3 } from 'aws-sdk'; - -import type { StorageConfig } from '../types'; - -export const save = async (args: { - id: string; - content: string | Buffer; - config: StorageConfig; -}): Promise => { - const { id, content, config } = args; - if (!config.s3) { - throw new Error('S3 config not provided'); - } - const s3 = new S3({ - region: config.s3.region, - accessKeyId: config.s3.accessKeyId, - secretAccessKey: config.s3.secretAccessKey, - }); - await s3 - .putObject({ - Bucket: config.s3.bucket, - Key: id, - Body: content, - }) - .promise(); -}; - -export const retrieve = async (args: { - id: string; - config: StorageConfig; -}): Promise => { - const { id, config } = args; - if (!config.s3) { - throw new Error('S3 config not provided'); - } - const s3 = new S3({ - region: config.s3.region, - accessKeyId: config.s3.accessKeyId, - secretAccessKey: config.s3.secretAccessKey, - }); - const data = await s3 - .getObject({ - Bucket: config.s3.bucket, - Key: id, - }) - .promise(); - return data.Body as Buffer; -}; - -export const deleteFile = async (args: { - id: string; - config: StorageConfig; -}): Promise => { - const { id, config } = args; - if (!config.s3) { - throw new Error('S3 config not provided'); - } - const s3 = new S3({ - region: config.s3.region, - accessKeyId: config.s3.accessKeyId, - secretAccessKey: config.s3.secretAccessKey, - }); - await s3 - .deleteObject({ - Bucket: config.s3.bucket, - Key: id, - }) - .promise(); -}; diff --git a/packages/files-core/src/types.ts b/packages/files-core/src/types.ts deleted file mode 100644 index dfbc8652..00000000 --- a/packages/files-core/src/types.ts +++ /dev/null @@ -1,39 +0,0 @@ -export interface StorageConfig { - type: 'local' | 's3' | 'gcs'; - local?: { - path: string; - }; - s3?: { - bucket: string; - region: string; - accessKeyId: string; - secretAccessKey: string; - }; - gcs?: { - bucket: string; - keyFilename?: string; - projectId?: string; - }; -} - -export interface UploadOptions { - contentType?: string; - metadata?: Record; -} - -export interface FileData { - id: string; - content: string | Buffer; -} - -export interface FileRecord { - id: string; - filename?: string; - contentType?: string; - size?: number; - storageType: 'local' | 's3' | 'gcs'; - storagePath: string; - metadata?: Record; - createdAt: Date; - updatedAt: Date; -} diff --git a/packages/files-core/tsconfig.json b/packages/files-core/tsconfig.json deleted file mode 100644 index fce4d054..00000000 --- a/packages/files-core/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "@ttoss/config/tsconfig.json" -} diff --git a/packages/files-core/tsup.config.ts b/packages/files-core/tsup.config.ts deleted file mode 100644 index 8db39855..00000000 --- a/packages/files-core/tsup.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { tsupConfig } from '@ttoss/config'; - -export default tsupConfig(); diff --git a/packages/postgresdb/jest.config.ts b/packages/postgresdb/jest.config.ts deleted file mode 100644 index c94e5234..00000000 --- a/packages/postgresdb/jest.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { jestRootConfig } from '@ttoss/config'; - -export default jestRootConfig(); diff --git a/packages/postgresdb/package.json b/packages/postgresdb/package.json index dd48d37c..11e11e99 100644 --- a/packages/postgresdb/package.json +++ b/packages/postgresdb/package.json @@ -4,12 +4,14 @@ "version": "0.0.0-alpha.2", "description": "Database models and operations for SOAT packages", "type": "module", - "exports": "./src/index.ts", - "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/esm/index.js" + } + }, "scripts": { "build": "tsup", - "pretest": "pnpm run build", - "test": "jest --projects tests/unit", "sync": "ttoss-postgresdb sync", "db-dev:start": "docker compose -f docker-compose.dev.yml up -d", "db-dev:stop": "docker compose -f docker-compose.dev.yml down", @@ -17,17 +19,16 @@ "db-dev:logs": "docker compose -f docker-compose.dev.yml logs -f" }, "dependencies": { - "@ttoss/postgresdb": "^0.3.0" + "@ttoss/postgresdb": "^0.8.0", + "nanoid": "^5.1.7" }, "devDependencies": { - "@testcontainers/postgresql": "^11.11.0", - "@ttoss/config": "^1.35.12", - "@ttoss/postgresdb-cli": "^0.1.24", - "@ttoss/test-utils": "^4.0.2", - "@types/jest": "^30.0.0", - "@types/node": "^25.0.3", - "jest": "^30.2.0", + "@testcontainers/postgresql": "^11.13.0", + "@ttoss/config": "^1.37.8", + "@ttoss/postgresdb-cli": "^0.2.8", + "@ttoss/test-utils": "^4.2.8", + "@types/node": "^25.5.2", "tsup": "^8.5.1", - "typescript": "~5.9.3" + "typescript": "~6.0.2" } } diff --git a/packages/postgresdb/src/index.ts b/packages/postgresdb/src/index.ts index 27ddcb30..9a133291 100644 --- a/packages/postgresdb/src/index.ts +++ b/packages/postgresdb/src/index.ts @@ -1 +1,4 @@ export * as models from './models'; +export { AI_PROVIDER_SLUGS } from './models/AiProvider'; +export type { AiProviderSlug } from './models/AiProvider'; +export * from './utils/publicId'; diff --git a/packages/postgresdb/src/models/Actor.ts b/packages/postgresdb/src/models/Actor.ts new file mode 100644 index 00000000..88bac1f0 --- /dev/null +++ b/packages/postgresdb/src/models/Actor.ts @@ -0,0 +1,69 @@ +import { + BelongsTo, + Column, + DataType, + ForeignKey, + Model, + Table, +} from '@ttoss/postgresdb'; + +import { generatePublicId, PUBLIC_ID_PREFIXES } from '../utils/publicId'; +import { Project } from './Project'; + +@Table({ + tableName: 'actors', + hooks: { + beforeValidate: (instance: Actor) => { + if (!instance.publicId) { + instance.publicId = generatePublicId(PUBLIC_ID_PREFIXES.actor); + } + }, + }, + indexes: [ + { + unique: true, + fields: ['project_id', 'external_id'], + }, + ], +}) +export class Actor extends Model { + @Column({ + type: DataType.STRING(32), + unique: true, + allowNull: false, + }) + declare publicId: string; + + @ForeignKey(() => { + return Project; + }) + @Column({ type: DataType.INTEGER, allowNull: false }) + declare projectId: number; + + @BelongsTo(() => { + return Project; + }) + declare project: Project; + + @Column({ type: DataType.STRING, allowNull: false }) + declare name: string; + + @Column({ type: DataType.STRING }) + declare type?: string; + + @Column({ type: DataType.STRING }) + declare externalId?: string; + + @Column({ + type: DataType.JSONB, + allowNull: true, + defaultValue: {}, + }) + declare tags: Record | null; + + @Column({ type: DataType.DATE }) + declare createdAt: Date; + + @Column({ type: DataType.DATE }) + declare updatedAt: Date; +} diff --git a/packages/postgresdb/src/models/AiProvider.ts b/packages/postgresdb/src/models/AiProvider.ts new file mode 100644 index 00000000..ecb28a58 --- /dev/null +++ b/packages/postgresdb/src/models/AiProvider.ts @@ -0,0 +1,92 @@ +import { + BelongsTo, + Column, + DataType, + ForeignKey, + Model, + Table, +} from '@ttoss/postgresdb'; + +import { generatePublicId, PUBLIC_ID_PREFIXES } from '../utils/publicId'; +import { Project } from './Project'; +import { Secret } from './Secret'; + +export const AI_PROVIDER_SLUGS = [ + 'openai', + 'anthropic', + 'google', + 'xai', + 'groq', + 'ollama', + 'azure', + 'bedrock', + 'gateway', + 'custom', +] as const; + +export type AiProviderSlug = (typeof AI_PROVIDER_SLUGS)[number]; + +@Table({ + tableName: 'ai_providers', + hooks: { + beforeValidate: (instance: AiProvider) => { + if (!instance.publicId) { + instance.publicId = generatePublicId(PUBLIC_ID_PREFIXES.aiProvider); + } + }, + }, +}) +export class AiProvider extends Model { + @Column({ + type: DataType.STRING(32), + unique: true, + allowNull: false, + }) + declare publicId: string; + + @ForeignKey(() => { + return Project; + }) + @Column({ type: DataType.INTEGER, allowNull: false }) + declare projectId: number; + + @BelongsTo(() => { + return Project; + }) + declare project: Project; + + @ForeignKey(() => { + return Secret; + }) + @Column({ type: DataType.INTEGER, allowNull: true }) + declare secretId: number | null; + + @BelongsTo(() => { + return Secret; + }) + declare secret: Secret | null; + + @Column({ type: DataType.STRING, allowNull: false }) + declare name: string; + + @Column({ + type: DataType.ENUM(...AI_PROVIDER_SLUGS), + allowNull: false, + }) + declare provider: AiProviderSlug; + + @Column({ type: DataType.STRING, allowNull: false }) + declare defaultModel: string; + + @Column({ type: DataType.STRING, allowNull: true }) + declare baseUrl: string | null; + + @Column({ type: DataType.JSONB, allowNull: true }) + declare config: Record | null; + + @Column({ type: DataType.DATE }) + declare createdAt: Date; + + @Column({ type: DataType.DATE }) + declare updatedAt: Date; +} diff --git a/packages/postgresdb/src/models/Conversation.ts b/packages/postgresdb/src/models/Conversation.ts new file mode 100644 index 00000000..a28d1b35 --- /dev/null +++ b/packages/postgresdb/src/models/Conversation.ts @@ -0,0 +1,68 @@ +import { + BelongsTo, + Column, + DataType, + ForeignKey, + HasMany, + Model, + Table, +} from '@ttoss/postgresdb'; + +import { generatePublicId, PUBLIC_ID_PREFIXES } from '../utils/publicId'; +import { ConversationMessage } from './ConversationMessage'; +import { Project } from './Project'; + +@Table({ + tableName: 'conversations', + hooks: { + beforeValidate: (instance: Conversation) => { + if (!instance.publicId) { + instance.publicId = generatePublicId(PUBLIC_ID_PREFIXES.conversation); + } + }, + }, +}) +export class Conversation extends Model { + @Column({ + type: DataType.STRING(32), + unique: true, + allowNull: false, + }) + declare publicId: string; + + @ForeignKey(() => { + return Project; + }) + @Column({ type: DataType.INTEGER, allowNull: false }) + declare projectId: number; + + @BelongsTo(() => { + return Project; + }) + declare project: Project; + + @Column({ + type: DataType.STRING, + allowNull: false, + defaultValue: 'open', + }) + declare status: string; + + @Column({ + type: DataType.JSONB, + allowNull: true, + defaultValue: {}, + }) + declare tags: Record | null; + + @HasMany(() => { + return ConversationMessage; + }) + declare messages: ConversationMessage[]; + + @Column({ type: DataType.DATE }) + declare createdAt: Date; + + @Column({ type: DataType.DATE }) + declare updatedAt: Date; +} diff --git a/packages/postgresdb/src/models/ConversationMessage.ts b/packages/postgresdb/src/models/ConversationMessage.ts new file mode 100644 index 00000000..db451909 --- /dev/null +++ b/packages/postgresdb/src/models/ConversationMessage.ts @@ -0,0 +1,59 @@ +import { + BelongsTo, + Column, + DataType, + ForeignKey, + Model, + Table, +} from '@ttoss/postgresdb'; + +import { Actor } from './Actor'; +import { Conversation } from './Conversation'; +import { Document } from './Document'; + +@Table({ + tableName: 'conversation_messages', + indexes: [ + { + unique: true, + fields: ['conversation_id', 'document_id'], + }, + ], +}) +export class ConversationMessage extends Model { + @ForeignKey(() => { + return Conversation; + }) + @Column({ type: DataType.INTEGER, allowNull: false }) + declare conversationId: number; + + @BelongsTo(() => { + return Conversation; + }) + declare conversation: Conversation; + + @ForeignKey(() => { + return Document; + }) + @Column({ type: DataType.INTEGER, allowNull: false }) + declare documentId: number; + + @BelongsTo(() => { + return Document; + }) + declare document: Document; + + @ForeignKey(() => { + return Actor; + }) + @Column({ type: DataType.INTEGER, allowNull: false }) + declare actorId: number; + + @BelongsTo(() => { + return Actor; + }) + declare actor: Actor; + + @Column({ type: DataType.INTEGER, allowNull: false }) + declare position: number; +} diff --git a/packages/postgresdb/src/models/Document.ts b/packages/postgresdb/src/models/Document.ts index d17631da..b07bcc25 100644 --- a/packages/postgresdb/src/models/Document.ts +++ b/packages/postgresdb/src/models/Document.ts @@ -1,31 +1,67 @@ -import { Column, DataType, Model, Table } from '@ttoss/postgresdb'; +import { + BelongsTo, + Column, + DataType, + ForeignKey, + Model, + Table, +} from '@ttoss/postgresdb'; -@Table({ tableName: 'documents' }) +import { generatePublicId, PUBLIC_ID_PREFIXES } from '../utils/publicId'; +import { File } from './File'; + +@Table({ + tableName: 'documents', + hooks: { + beforeValidate: (instance: Document) => { + if (!instance.publicId) { + instance.publicId = generatePublicId(PUBLIC_ID_PREFIXES.document); + } + }, + }, +}) export class Document extends Model { @Column({ - type: DataType.UUID, - primaryKey: true, - defaultValue: DataType.UUIDV4, + type: DataType.STRING(32), + unique: true, + allowNull: false, }) - declare id: string; + declare publicId: string; - @Column({ type: DataType.STRING }) - declare title?: string; + @ForeignKey(() => { + return File; + }) + @Column({ type: DataType.INTEGER, allowNull: false, unique: true }) + declare fileId: number; - @Column({ type: DataType.UUID }) - declare fileId: string; + @BelongsTo(() => { + return File; + }) + declare file: File; - @Column({ type: DataType.STRING }) - declare embeddingModel?: string; + @Column({ + type: DataType.STRING, + allowNull: true, + }) + declare title: string | null; - @Column({ type: DataType.STRING }) - declare embeddingProvider?: string; + @Column({ + type: DataType.TEXT, + allowNull: true, + }) + declare metadata: string | null; - @Column({ type: DataType.VECTOR(1536) }) - declare embedding?: number[]; + @Column({ + type: DataType.JSONB, + allowNull: true, + }) + declare tags: Record | null; - @Column({ type: DataType.TEXT }) - declare metadata?: string; // JSON string + @Column({ + type: DataType.VECTOR(1024), + allowNull: true, + }) + declare embedding: number[] | null; @Column({ type: DataType.DATE }) declare createdAt: Date; diff --git a/packages/postgresdb/src/models/File.ts b/packages/postgresdb/src/models/File.ts index 94c40bb4..1f9f816b 100644 --- a/packages/postgresdb/src/models/File.ts +++ b/packages/postgresdb/src/models/File.ts @@ -1,13 +1,43 @@ -import { Column, DataType, Model, Table } from '@ttoss/postgresdb'; +import { + BelongsTo, + Column, + DataType, + ForeignKey, + Model, + Table, +} from '@ttoss/postgresdb'; -@Table({ tableName: 'files' }) +import { generatePublicId, PUBLIC_ID_PREFIXES } from '../utils/publicId'; +import { Project } from './Project'; + +@Table({ + tableName: 'files', + hooks: { + beforeValidate: (instance: File) => { + if (!instance.publicId) { + instance.publicId = generatePublicId(PUBLIC_ID_PREFIXES.file); + } + }, + }, +}) export class File extends Model { @Column({ - type: DataType.UUID, - primaryKey: true, - defaultValue: DataType.UUIDV4, + type: DataType.STRING(32), + unique: true, + allowNull: false, + }) + declare publicId: string; + + @ForeignKey(() => { + return Project; + }) + @Column({ type: DataType.INTEGER, allowNull: false }) + declare projectId: number; + + @BelongsTo(() => { + return Project; }) - declare id: string; + declare project: Project; @Column({ type: DataType.STRING }) declare filename?: string; @@ -27,6 +57,13 @@ export class File extends Model { @Column({ type: DataType.TEXT }) declare metadata?: string; // JSON string + @Column({ + type: DataType.JSONB, + allowNull: true, + defaultValue: {}, + }) + declare tags: Record | null; + @Column({ type: DataType.DATE }) declare createdAt: Date; diff --git a/packages/postgresdb/src/models/Project.ts b/packages/postgresdb/src/models/Project.ts new file mode 100644 index 00000000..5e6228e0 --- /dev/null +++ b/packages/postgresdb/src/models/Project.ts @@ -0,0 +1,34 @@ +import { Column, DataType, HasMany, Model, Table } from '@ttoss/postgresdb'; + +import { generatePublicId, PUBLIC_ID_PREFIXES } from '../utils/publicId'; + +@Table({ + tableName: 'projects', + hooks: { + beforeValidate: (instance: Project) => { + if (!instance.publicId) { + instance.publicId = generatePublicId(PUBLIC_ID_PREFIXES.project); + } + }, + }, +}) +export class Project extends Model { + @Column({ + type: DataType.STRING(32), + unique: true, + allowNull: false, + }) + declare publicId: string; + + @Column({ + type: DataType.STRING, + allowNull: false, + }) + declare name: string; + + @Column({ type: DataType.DATE }) + declare createdAt: Date; + + @Column({ type: DataType.DATE }) + declare updatedAt: Date; +} diff --git a/packages/postgresdb/src/models/ProjectKey.ts b/packages/postgresdb/src/models/ProjectKey.ts new file mode 100644 index 00000000..0f9fe3a4 --- /dev/null +++ b/packages/postgresdb/src/models/ProjectKey.ts @@ -0,0 +1,103 @@ +import { + BelongsTo, + Column, + DataType, + ForeignKey, + Model, + Table, +} from '@ttoss/postgresdb'; + +import { generatePublicId, PUBLIC_ID_PREFIXES } from '../utils/publicId'; +import { Project } from './Project'; +import { ProjectPolicy } from './ProjectPolicy'; +import { User } from './User'; + +@Table({ + tableName: 'project_keys', + hooks: { + beforeValidate: (instance: ProjectKey) => { + if (!instance.publicId) { + instance.publicId = generatePublicId(PUBLIC_ID_PREFIXES.projectKey); + } + }, + }, +}) +export class ProjectKey extends Model { + @Column({ + type: DataType.STRING(32), + unique: true, + allowNull: false, + }) + declare publicId: string; + + @ForeignKey(() => { + return User; + }) + @Column({ type: DataType.INTEGER, allowNull: false }) + declare userId: number; + + @BelongsTo(() => { + return User; + }) + declare user: User; + + @ForeignKey(() => { + return Project; + }) + @Column({ type: DataType.INTEGER, allowNull: false }) + declare projectId: number; + + @BelongsTo( + () => { + return Project; + }, + { onDelete: 'CASCADE' } + ) + declare project: Project; + + @ForeignKey(() => { + return ProjectPolicy; + }) + @Column({ type: DataType.INTEGER, allowNull: false }) + declare policyId: number; + + @BelongsTo( + () => { + return ProjectPolicy; + }, + { onDelete: 'CASCADE' } + ) + declare policy: ProjectPolicy; + + @Column({ + type: DataType.STRING, + allowNull: false, + }) + declare name: string; + + /** + * First 8 characters of the raw project key. Stored in plaintext to allow + * fast DB lookup before running bcrypt.compare against keyHash. + */ + @Column({ + type: DataType.STRING(8), + allowNull: false, + }) + declare keyPrefix: string; + + /** + * Bcrypt hash of the raw project key value (pk_). + * The raw key is shown once at creation and never stored in plaintext. + */ + @Column({ + type: DataType.STRING, + allowNull: false, + }) + declare keyHash: string; + + @Column({ type: DataType.DATE }) + declare createdAt: Date; + + @Column({ type: DataType.DATE }) + declare updatedAt: Date; +} diff --git a/packages/postgresdb/src/models/ProjectPolicy.ts b/packages/postgresdb/src/models/ProjectPolicy.ts new file mode 100644 index 00000000..43ce4b12 --- /dev/null +++ b/packages/postgresdb/src/models/ProjectPolicy.ts @@ -0,0 +1,68 @@ +import { + BelongsTo, + Column, + DataType, + ForeignKey, + Model, + Table, +} from '@ttoss/postgresdb'; + +import { generatePublicId, PUBLIC_ID_PREFIXES } from '../utils/publicId'; +import { Project } from './Project'; + +@Table({ + tableName: 'project_policies', + hooks: { + beforeValidate: (instance: ProjectPolicy) => { + if (!instance.publicId) { + instance.publicId = generatePublicId(PUBLIC_ID_PREFIXES.policy); + } + }, + }, +}) +export class ProjectPolicy extends Model { + @Column({ + type: DataType.STRING(32), + unique: true, + allowNull: false, + }) + declare publicId: string; + + @ForeignKey(() => { + return Project; + }) + @Column({ type: DataType.INTEGER, allowNull: false }) + declare projectId: number; + + @BelongsTo( + () => { + return Project; + }, + { onDelete: 'CASCADE' } + ) + declare project: Project; + + @Column({ + type: DataType.STRING, + allowNull: true, + }) + declare name: string | null; + + @Column({ + type: DataType.TEXT, + allowNull: true, + }) + declare description: string | null; + + @Column({ + type: DataType.JSONB, + allowNull: false, + }) + declare document: object; + + @Column({ type: DataType.DATE }) + declare createdAt: Date; + + @Column({ type: DataType.DATE }) + declare updatedAt: Date; +} diff --git a/packages/postgresdb/src/models/Secret.ts b/packages/postgresdb/src/models/Secret.ts new file mode 100644 index 00000000..46b97004 --- /dev/null +++ b/packages/postgresdb/src/models/Secret.ts @@ -0,0 +1,53 @@ +import { + BelongsTo, + Column, + DataType, + ForeignKey, + Model, + Table, +} from '@ttoss/postgresdb'; + +import { generatePublicId, PUBLIC_ID_PREFIXES } from '../utils/publicId'; +import { Project } from './Project'; + +@Table({ + tableName: 'secrets', + hooks: { + beforeValidate: (instance: Secret) => { + if (!instance.publicId) { + instance.publicId = generatePublicId(PUBLIC_ID_PREFIXES.secret); + } + }, + }, +}) +export class Secret extends Model { + @Column({ + type: DataType.STRING(32), + unique: true, + allowNull: false, + }) + declare publicId: string; + + @ForeignKey(() => { + return Project; + }) + @Column({ type: DataType.INTEGER, allowNull: false }) + declare projectId: number; + + @BelongsTo(() => { + return Project; + }) + declare project: Project; + + @Column({ type: DataType.STRING, allowNull: false }) + declare name: string; + + @Column({ type: DataType.TEXT, allowNull: true }) + declare encryptedValue: string | null; + + @Column({ type: DataType.DATE }) + declare createdAt: Date; + + @Column({ type: DataType.DATE }) + declare updatedAt: Date; +} diff --git a/packages/postgresdb/src/models/User.ts b/packages/postgresdb/src/models/User.ts new file mode 100644 index 00000000..542ce11c --- /dev/null +++ b/packages/postgresdb/src/models/User.ts @@ -0,0 +1,48 @@ +import { Column, DataType, Model, Table } from '@ttoss/postgresdb'; + +import { generatePublicId, PUBLIC_ID_PREFIXES } from '../utils/publicId'; + +@Table({ + tableName: 'users', + hooks: { + beforeValidate: (instance: User) => { + if (!instance.publicId) { + instance.publicId = generatePublicId(PUBLIC_ID_PREFIXES.user); + } + }, + }, +}) +export class User extends Model { + @Column({ + type: DataType.STRING(32), + unique: true, + allowNull: false, + }) + declare publicId: string; + + @Column({ + type: DataType.STRING, + unique: true, + allowNull: false, + }) + declare username: string; + + @Column({ + type: DataType.STRING, + allowNull: false, + }) + declare passwordHash: string; + + @Column({ + type: DataType.ENUM('admin', 'user'), + allowNull: false, + defaultValue: 'user', + }) + declare role: 'admin' | 'user'; + + @Column({ type: DataType.DATE }) + declare createdAt: Date; + + @Column({ type: DataType.DATE }) + declare updatedAt: Date; +} diff --git a/packages/postgresdb/src/models/UserProject.ts b/packages/postgresdb/src/models/UserProject.ts new file mode 100644 index 00000000..91d55ffc --- /dev/null +++ b/packages/postgresdb/src/models/UserProject.ts @@ -0,0 +1,55 @@ +import { + BelongsTo, + Column, + DataType, + ForeignKey, + Model, + Table, +} from '@ttoss/postgresdb'; + +import { Project } from './Project'; +import { User } from './User'; + +@Table({ + tableName: 'user_projects', + indexes: [{ unique: true, fields: ['user_id', 'project_id'] }], +}) +export class UserProject extends Model { + @ForeignKey(() => { + return User; + }) + @Column({ type: DataType.INTEGER, allowNull: false }) + declare userId: number; + + @BelongsTo(() => { + return User; + }) + declare user: User; + + @ForeignKey(() => { + return Project; + }) + @Column({ type: DataType.INTEGER, allowNull: false }) + declare projectId: number; + + @BelongsTo( + () => { + return Project; + }, + { onDelete: 'CASCADE' } + ) + declare project: Project; + + @Column({ + type: DataType.ARRAY(DataType.INTEGER), + allowNull: false, + defaultValue: [], + }) + declare policyIds: number[]; + + @Column({ type: DataType.DATE }) + declare createdAt: Date; + + @Column({ type: DataType.DATE }) + declare updatedAt: Date; +} diff --git a/packages/postgresdb/src/models/index.ts b/packages/postgresdb/src/models/index.ts index 88da204a..b7ae945a 100644 --- a/packages/postgresdb/src/models/index.ts +++ b/packages/postgresdb/src/models/index.ts @@ -1,2 +1,12 @@ +export { Actor } from './Actor'; +export { AiProvider } from './AiProvider'; +export { ProjectKey } from './ProjectKey'; +export { Conversation } from './Conversation'; +export { ConversationMessage } from './ConversationMessage'; export { Document } from './Document'; export { File } from './File'; +export { Project } from './Project'; +export { ProjectPolicy } from './ProjectPolicy'; +export { Secret } from './Secret'; +export { User } from './User'; +export { UserProject } from './UserProject'; diff --git a/packages/postgresdb/src/utils/publicId.ts b/packages/postgresdb/src/utils/publicId.ts new file mode 100644 index 00000000..11a9a0ea --- /dev/null +++ b/packages/postgresdb/src/utils/publicId.ts @@ -0,0 +1,57 @@ +import { customAlphabet } from 'nanoid'; + +/** + * Public ID prefixes for each entity type (Stripe-style) + * They can have 2 to 6 characters before the underscore. + */ +export const PUBLIC_ID_PREFIXES = { + file: 'file_', + user: 'usr_', + project: 'proj_', + policy: 'pol_', + projectKey: 'key_', + document: 'doc_', + actor: 'act_', + conversation: 'conv_', + secret: 'sec_', + aiProvider: 'aip_', +} as const; + +/** + * Prefix for raw project key values (shown once at creation, then hashed). + * Format: sk_{random} — distinguishable from JWTs (which start with 'eyJ'). + */ +export const PROJECT_KEY_RAW_PREFIX = 'sk_'; + +export type PublicIdPrefix = + (typeof PUBLIC_ID_PREFIXES)[keyof typeof PUBLIC_ID_PREFIXES]; + +/** + * Generates a Stripe-style public ID with the given prefix. + * Format: {prefix}{16-character nanoid} + * Example: file_V1StGXR8Z5jdHi6B + * + * @param prefix - The prefix for the entity type (e.g., 'file_') + * @returns A unique public ID string + */ +export const generatePublicId = (prefix: PublicIdPrefix): string => { + const nanoid = customAlphabet( + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', + 16 + ); + return `${prefix}${nanoid()}`; +}; + +/** + * Validates if a string matches a public ID format + * @param id - The ID to validate + * @param prefix - Expected prefix + * @returns true if valid + */ +export const isValidPublicId = ( + id: string, + prefix: PublicIdPrefix +): boolean => { + const pattern = new RegExp(`^${prefix}[A-Za-z0-9]{16}$`); + return pattern.test(id); +}; diff --git a/packages/postgresdb/tests/tsconfig.json b/packages/postgresdb/tests/tsconfig.json deleted file mode 100644 index bcb13129..00000000 --- a/packages/postgresdb/tests/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@ttoss/config/tsconfig.test.json", - "compilerOptions": { - "paths": { - "dist/*": ["../dist/*"], - "src/*": ["../src/*"], - "tests/*": ["./*"] - } - } -} diff --git a/packages/postgresdb/tests/unit/babel.config.cjs b/packages/postgresdb/tests/unit/babel.config.cjs deleted file mode 100644 index 9c95923b..00000000 --- a/packages/postgresdb/tests/unit/babel.config.cjs +++ /dev/null @@ -1,5 +0,0 @@ -const { babelConfig } = require('@ttoss/config'); - -const config = babelConfig({}); - -module.exports = config; diff --git a/packages/postgresdb/tests/unit/jest.config.ts b/packages/postgresdb/tests/unit/jest.config.ts deleted file mode 100644 index b44e2f2b..00000000 --- a/packages/postgresdb/tests/unit/jest.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { jestUnitConfig } from '@ttoss/config'; -import { getTransformIgnorePatterns } from '@ttoss/test-utils'; - -export default jestUnitConfig({ - transformIgnorePatterns: getTransformIgnorePatterns({ - esmModules: ['@ttoss/postgresdb'], - }), -}); diff --git a/packages/postgresdb/tests/unit/tests/File.test.ts b/packages/postgresdb/tests/unit/tests/File.test.ts deleted file mode 100644 index 87e9f477..00000000 --- a/packages/postgresdb/tests/unit/tests/File.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -import type { StartedPostgreSqlContainer } from '@testcontainers/postgresql'; -import { PostgreSqlContainer } from '@testcontainers/postgresql'; -import type { Sequelize } from '@ttoss/postgresdb'; -import { initialize } from '@ttoss/postgresdb'; -import { models } from 'dist/index'; - -let sequelize: Sequelize; -let postgresContainer: StartedPostgreSqlContainer; - -jest.setTimeout(60000); - -beforeAll(async () => { - // Start PostgreSQL container - postgresContainer = await new PostgreSqlContainer( - 'pgvector/pgvector:0.8.1-pg18-trixie' - ).start(); - - // Initialize database with container credentials - const db = await initialize({ - createVectorExtension: true, - models, - logging: false, - username: postgresContainer.getUsername(), - password: postgresContainer.getPassword(), - database: postgresContainer.getDatabase(), - host: postgresContainer.getHost(), - port: postgresContainer.getPort(), - }); - - sequelize = db.sequelize; - - // Sync database schema - await sequelize.sync(); -}); - -afterAll(async () => { - await sequelize.close(); - await postgresContainer.stop(); -}); - -describe('File model', () => { - test('should create and retrieve file', async () => { - const metadata = { uploadedBy: 'test-user' }; - const fileData = { - filename: 'test.txt', - contentType: 'text/plain', - size: 1024, - storageType: 'local' as const, - storagePath: '/tmp/test-file-id', - metadata: JSON.stringify(metadata), - }; - - const file = await models.File.create(fileData); - - expect(file.filename).toBe(fileData.filename); - expect(file.contentType).toBe(fileData.contentType); - expect(file.size).toBe(fileData.size); - expect(file.storageType).toBe(fileData.storageType); - expect(file.storagePath).toBe(fileData.storagePath); - expect(JSON.parse(file.metadata!)).toEqual(metadata); - - const foundFile = await models.File.findByPk(file.id); - - expect(foundFile).toMatchObject({ - id: file.id, - filename: fileData.filename, - contentType: fileData.contentType, - size: fileData.size, - storageType: fileData.storageType, - storagePath: fileData.storagePath, - }); - expect(JSON.parse(foundFile!.metadata!)).toEqual(metadata); - }); - - test('should update file metadata', async () => { - const fileData = { - filename: 'update-test.txt', - contentType: 'text/plain', - size: 512, - storageType: 's3' as const, - storagePath: 's3://bucket/update-test-file-id', - }; - - const file = await models.File.create(fileData); - - // Update metadata - const newMetadata = { updatedBy: 'admin', version: 2 }; - await file.update({ - metadata: JSON.stringify(newMetadata), - size: 1024, - }); - - const updatedFile = await models.File.findByPk(file.id); - - expect(updatedFile!.size).toBe(1024); - expect(JSON.parse(updatedFile!.metadata!)).toEqual(newMetadata); - }); - - test('should delete file', async () => { - const fileData = { - filename: 'delete-test.txt', - contentType: 'application/json', - size: 256, - storageType: 'gcs' as const, - storagePath: 'gs://bucket/delete-test-file-id', - }; - - const file = await models.File.create(fileData); - - const createdFile = await models.File.findByPk(file.id); - expect(createdFile).toBeTruthy(); - - await createdFile!.destroy(); - - const deletedFile = await models.File.findByPk(file.id); - expect(deletedFile).toBeNull(); - }); - - test('should handle files without metadata', async () => { - const fileData = { - contentType: 'image/png', - size: 2048, - storageType: 'local' as const, - storagePath: '/tmp/no-metadata-file-id', - }; - - const file = await models.File.create(fileData); - - expect(file.metadata).toBeNull(); - - const foundFile = await models.File.findByPk(file.id); - expect(foundFile!.metadata).toBeNull(); - }); -}); diff --git a/packages/server/.env.example b/packages/server/.env.example index 9a32f497..d65047db 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -6,4 +6,16 @@ DATABASE_USER=soat_user DATABASE_PASSWORD=soat_password # Server Configuration -PORT=5047 \ No newline at end of file +PORT=5047 + +# Embedding Configuration +EMBEDDING_PROVIDER=ollama +EMBEDDING_MODEL=qwen3-embedding:0.6b +EMBEDDING_DIMENSIONS=1024 +OLLAMA_BASE_URL=http://localhost:11434 + +# Embedding Configuration +EMBEDDING_PROVIDER=ollama +EMBEDDING_MODEL=qwen3-embedding:0.6b +EMBEDDING_DIMENSIONS=1024 +OLLAMA_BASE_URL=http://localhost:11434 \ No newline at end of file diff --git a/packages/server/package.json b/packages/server/package.json index 2930437b..344c7019 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -6,28 +6,32 @@ "build": "tsup", "dev": "tsx watch src/server.ts", "lint-openapi": "spectral lint packages/server/src/rest/openapi/**/*.yaml", + "pretest": "pnpm run --filter @soat/postgresdb build", "test": "jest --projects tests/unit" }, + "type": "module", "dependencies": { - "@soat/documents-core": "workspace:*", - "@soat/embeddings-core": "workspace:*", - "@soat/files-core": "workspace:*", "@soat/postgresdb": "workspace:*", - "@ttoss/http-server": "^0.3.2", - "@ttoss/http-server-mcp": "^0.3.2", - "@ttoss/postgresdb": "^0.3.0", - "dotenv": "^17.2.3", + "@ttoss/http-server": "^0.5.9", + "@ttoss/http-server-mcp": "^0.11.1", + "@ttoss/postgresdb": "^0.8.0", + "bcryptjs": "^3.0.3", + "dotenv": "^17.4.1", + "jsonwebtoken": "^9.0.3", "ollama": "^0.6.3", - "pg": "^8.16.3" + "pg": "^8.20.0" }, "devDependencies": { "@stoplight/spectral-cli": "^6.15.0", - "@ttoss/config": "^1.35.12", - "@ttoss/test-utils": "^4.0.2", + "@testcontainers/postgresql": "^11.13.0", + "@ttoss/config": "^1.37.8", + "@ttoss/test-utils": "^4.2.8", + "@types/bcryptjs": "^3.0.0", "@types/jest": "^30.0.0", - "@types/pg": "^8.16.0", - "@types/supertest": "^6.0.3", - "jest": "^30.2.0", + "@types/jsonwebtoken": "^9.0.10", + "@types/pg": "^8.20.0", + "@types/supertest": "^7.2.0", + "jest": "^30.3.0", "supertest": "^7.2.2", "tsup": "^8.5.1", "tsx": "^4.21.0" diff --git a/packages/server/src/Context.ts b/packages/server/src/Context.ts index f9f078e2..0e980b61 100644 --- a/packages/server/src/Context.ts +++ b/packages/server/src/Context.ts @@ -1,2 +1,32 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -export type Context = any; +import type { DB } from './db'; + +export type AuthUser = { + id: number; + publicId: string; + username: string; + role: 'admin' | 'user'; + isAllowed: (args: { + projectPublicId: string; + action: string; + resource?: string; + context?: Record; + }) => Promise; + /** + * Resolves the internal project IDs the caller may access for the given action. + * - Explicit projectPublicId: verifies permission and returns [id], or null if forbidden/not found. + * - project key (no explicit id): infers from the key's scoped project. + * - JWT admin (no explicit id): returns undefined (no filter — all projects). + * - JWT user (no explicit id): enumerates all projects the user has access to. + */ + resolveProjectIds: (args: { + projectPublicId?: string; + action: string; + }) => Promise; + projectKeyProjectId?: string; +}; + +export type Context = { + db: DB; + authUser?: AuthUser; + // eslint-disable-next-line @typescript-eslint/no-explicit-any +} & Record; diff --git a/packages/server/src/app.ts b/packages/server/src/app.ts index 9c3b2ffb..b5856946 100644 --- a/packages/server/src/app.ts +++ b/packages/server/src/app.ts @@ -1,16 +1,21 @@ -import { App, bodyParser, cors } from '@ttoss/http-server'; +import { App, addHealthCheck, bodyParser, cors } from '@ttoss/http-server'; -// import { mcpRouter } from './mcp'; +import { mcpRouter } from './mcp/server'; +import { authMiddleware } from './middleware/auth'; import { restRouter } from './rest/router'; const app = new App(); +addHealthCheck({ app }); + app.use(cors()); app.use(bodyParser()); - -// app.use(mcpRouter.routes()); +app.use(authMiddleware); app.use(restRouter.routes()); app.use(restRouter.allowedMethods()); +app.use(mcpRouter.routes()); +app.use(mcpRouter.allowedMethods()); + export { app }; diff --git a/packages/server/src/db.ts b/packages/server/src/db.ts new file mode 100644 index 00000000..7d7e05da --- /dev/null +++ b/packages/server/src/db.ts @@ -0,0 +1,25 @@ +import { models } from '@soat/postgresdb'; +import type { App } from '@ttoss/http-server'; +import { initialize } from '@ttoss/postgresdb'; + +export { models }; + +export type DB = Awaited>>; + +export let db: DB; + +export const initializeDatabase = async (app: App) => { + db = await initialize({ + models, + createVectorExtension: true, + host: process.env.DATABASE_HOST, + port: Number(process.env.DATABASE_PORT), + database: process.env.DATABASE_NAME, + username: process.env.DATABASE_USER, + password: process.env.DATABASE_PASSWORD, + }); + + app.context.db = db; + + return db; +}; diff --git a/packages/server/src/lib/actors.ts b/packages/server/src/lib/actors.ts new file mode 100644 index 00000000..0d8079e6 --- /dev/null +++ b/packages/server/src/lib/actors.ts @@ -0,0 +1,176 @@ +import { Op } from '@ttoss/postgresdb'; + +import { db } from '../db'; + +const mapActor = ( + actor: InstanceType<(typeof db)['Actor']> & { + project?: InstanceType<(typeof db)['Project']>; + } +) => { + return { + id: actor.publicId, + projectId: actor.project?.publicId, + name: actor.name, + type: actor.type ?? undefined, + externalId: actor.externalId ?? undefined, + tags: actor.tags ?? undefined, + createdAt: actor.createdAt, + updatedAt: actor.updatedAt, + }; +}; + +export const listActors = async (args: { + projectIds?: number[]; + externalId?: string; + name?: string; + type?: string; + limit?: number; + offset?: number; +}) => { + const limit = args.limit ?? 50; + const offset = args.offset ?? 0; + + if (args.projectIds !== undefined && args.projectIds.length === 0) { + return { data: [], total: 0, limit, offset }; + } + + const where: Record = {}; + + if (args.projectIds !== undefined) { + where.projectId = args.projectIds; + } + + if (args.externalId !== undefined) { + where.externalId = args.externalId; + } + + if (args.name !== undefined) { + where.name = { [Op.iLike]: `%${args.name}%` }; + } + + if (args.type !== undefined) { + where.type = args.type; + } + + const { count, rows } = await db.Actor.findAndCountAll({ + where: Object.keys(where).length > 0 ? where : undefined, + include: [{ model: db.Project, as: 'project' }], + limit, + offset, + }); + + return { data: rows.map(mapActor), total: count, limit, offset }; +}; + +export const getActor = async (args: { id: string }) => { + const actor = await db.Actor.findOne({ + where: { publicId: args.id }, + include: [{ model: db.Project, as: 'project' }], + }); + + if (!actor) { + return null; + } + + return mapActor(actor); +}; + +export const createActor = async (args: { + projectId: number; + name: string; + type?: string; + externalId?: string; +}) => { + const actor = await db.Actor.create({ + projectId: args.projectId, + name: args.name, + type: args.type, + externalId: args.externalId, + }); + + const actorWithProject = await db.Actor.findOne({ + where: { id: actor.id }, + include: [{ model: db.Project, as: 'project' }], + }); + + return mapActor(actorWithProject!); +}; + +export const deleteActor = async (args: { id: string }) => { + const actor = await db.Actor.findOne({ where: { publicId: args.id } }); + + if (!actor) { + return null; + } + + await actor.destroy(); + + return { id: args.id }; +}; + +export const updateActor = async (args: { + id: string; + name?: string; + type?: string; + externalId?: string; +}) => { + const actor = await db.Actor.findOne({ where: { publicId: args.id } }); + + if (!actor) { + return null; + } + + const updates: Record = {}; + if (args.name !== undefined) { + updates.name = args.name; + } + if (args.type !== undefined) { + updates.type = args.type; + } + if (args.externalId !== undefined) { + updates.externalId = args.externalId; + } + + await actor.update(updates); + + const actorWithProject = await db.Actor.findOne({ + where: { id: actor.id }, + include: [{ model: db.Project, as: 'project' }], + }); + + return mapActor(actorWithProject!); +}; + +export const getActorTags = async (args: { id: string }) => { + const actor = await db.Actor.findOne({ where: { publicId: args.id } }); + + if (!actor) { + return null; + } + + return actor.tags ?? {}; +}; + +export const updateActorTags = async (args: { + id: string; + tags: Record; + merge?: boolean; +}) => { + const actor = await db.Actor.findOne({ where: { publicId: args.id } }); + + if (!actor) { + return null; + } + + const newTags = args.merge + ? { ...(actor.tags ?? {}), ...args.tags } + : args.tags; + await actor.update({ tags: newTags }); + + const actorWithProject = await db.Actor.findOne({ + where: { id: actor.id }, + include: [{ model: db.Project, as: 'project' }], + }); + + return mapActor(actorWithProject!); +}; diff --git a/packages/server/src/lib/agents.ts b/packages/server/src/lib/agents.ts new file mode 100644 index 00000000..7f8fc607 --- /dev/null +++ b/packages/server/src/lib/agents.ts @@ -0,0 +1,12 @@ +import { Ollama } from 'ollama'; + +export const streamAgent = async (args: { model: string; prompt: string }) => { + const host = process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434'; + const ollama = new Ollama({ host }); + + return ollama.chat({ + model: args.model, + messages: [{ role: 'user', content: args.prompt }], + stream: true, + }); +}; diff --git a/packages/server/src/lib/aiProviders.ts b/packages/server/src/lib/aiProviders.ts new file mode 100644 index 00000000..b314087d --- /dev/null +++ b/packages/server/src/lib/aiProviders.ts @@ -0,0 +1,139 @@ +import type { AiProviderSlug } from '@soat/postgresdb'; + +import { db } from 'src/db'; +import { decryptValue } from 'src/lib/secrets'; + +const getAiProviderIncludes = () => [ + { model: db.Project, as: 'project' }, + { model: db.Secret, as: 'secret' }, +]; + +const mapAiProvider = ( + instance: InstanceType<(typeof db)['AiProvider']> & { + project?: InstanceType<(typeof db)['Project']>; + secret?: InstanceType<(typeof db)['Secret']> | null; + } +) => ({ + id: instance.publicId, + projectId: instance.project?.publicId, + secretId: instance.secret?.publicId ?? null, + name: instance.name, + provider: instance.provider, + defaultModel: instance.defaultModel, + baseUrl: instance.baseUrl ?? undefined, + config: instance.config ?? undefined, + createdAt: instance.createdAt, + updatedAt: instance.updatedAt, +}); + +export const listAiProviders = async (args: { projectIds: number[] }) => { + const providers = await db.AiProvider.findAll({ + where: { projectId: args.projectIds }, + include: getAiProviderIncludes(), + }); + return providers.map(mapAiProvider); +}; + +export const getAiProvider = async (args: { id: string }) => { + const provider = await db.AiProvider.findOne({ + where: { publicId: args.id }, + include: getAiProviderIncludes(), + }); + if (!provider) return null; + return mapAiProvider(provider); +}; + +export const createAiProvider = async (args: { + projectId: number; + secretId?: number; + name: string; + provider: AiProviderSlug; + defaultModel: string; + baseUrl?: string; + config?: Record; +}) => { + const instance = await db.AiProvider.create({ + projectId: args.projectId, + secretId: args.secretId ?? null, + name: args.name, + provider: args.provider, + defaultModel: args.defaultModel, + baseUrl: args.baseUrl ?? null, + config: args.config ?? null, + }); + const withAssociations = await db.AiProvider.findOne({ + where: { id: instance.id }, + include: getAiProviderIncludes(), + }); + return mapAiProvider(withAssociations!); +}; + +export const updateAiProvider = async (args: { + id: string; + secretId?: number; + name?: string; + provider?: AiProviderSlug; + defaultModel?: string; + baseUrl?: string | null; + config?: Record | null; +}) => { + const instance = await db.AiProvider.findOne({ + where: { publicId: args.id }, + include: getAiProviderIncludes(), + }); + if (!instance) return null; + + if (args.name !== undefined) instance.name = args.name; + if (args.provider !== undefined) instance.provider = args.provider; + if (args.defaultModel !== undefined) + instance.defaultModel = args.defaultModel; + if (args.baseUrl !== undefined) instance.baseUrl = args.baseUrl; + if (args.config !== undefined) instance.config = args.config; + if (args.secretId !== undefined) instance.secretId = args.secretId; + + await instance.save(); + const refreshed = await db.AiProvider.findOne({ + where: { id: instance.id }, + include: getAiProviderIncludes(), + }); + return mapAiProvider(refreshed!); +}; + +export const deleteAiProvider = async (args: { + id: string; + force?: boolean; +}) => { + const instance = await db.AiProvider.findOne({ + where: { publicId: args.id }, + }); + if (!instance) return null; + + // TODO: add cascade check against chats when that module is implemented + await instance.destroy(); + return 'deleted' as const; +}; + +export const resolveAiProviderSecret = async (args: { + aiProviderId: string; +}) => { + const instance = await db.AiProvider.findOne({ + where: { publicId: args.aiProviderId }, + }); + if (!instance) return null; + + let decryptedValue: string | null = null; + if (instance.secretId) { + const secret = await db.Secret.findByPk(instance.secretId); + if (secret?.encryptedValue) { + decryptedValue = decryptValue(secret.encryptedValue); + } + } + + return { + provider: instance.provider, + defaultModel: instance.defaultModel, + baseUrl: instance.baseUrl ?? undefined, + config: instance.config ?? undefined, + secretValue: decryptedValue, + }; +}; diff --git a/packages/server/src/lib/conversations.ts b/packages/server/src/lib/conversations.ts new file mode 100644 index 00000000..f5459b57 --- /dev/null +++ b/packages/server/src/lib/conversations.ts @@ -0,0 +1,381 @@ +import fs from 'node:fs'; + +import { db } from '../db'; +import { createDocument, deleteDocument } from './documents'; + +const mapConversation = ( + conversation: InstanceType<(typeof db)['Conversation']> & { + project?: InstanceType<(typeof db)['Project']>; + } +) => { + return { + id: conversation.publicId, + projectId: conversation.project?.publicId, + status: conversation.status, + tags: conversation.tags ?? undefined, + createdAt: conversation.createdAt, + updatedAt: conversation.updatedAt, + }; +}; + +const mapMessage = ( + message: InstanceType<(typeof db)['ConversationMessage']> & { + document?: InstanceType<(typeof db)['Document']> & { + file?: InstanceType<(typeof db)['File']>; + }; + actor?: InstanceType<(typeof db)['Actor']>; + } +) => { + let content: string | null = null; + if (message.document?.file?.storagePath) { + try { + if (fs.existsSync(message.document.file.storagePath)) { + content = fs.readFileSync(message.document.file.storagePath, 'utf-8'); + } + } catch { + // Ignore read errors + } + } + + return { + documentId: message.document?.publicId, + actorId: message.actor?.publicId, + position: message.position, + content, + }; +}; + +export const listConversations = async (args: { + projectIds?: number[]; + actorId?: string; + limit?: number; + offset?: number; +}) => { + const limit = args.limit ?? 50; + const offset = args.offset ?? 0; + + if (args.projectIds !== undefined && args.projectIds.length === 0) { + return { data: [], total: 0, limit, offset }; + } + + const where: Record = {}; + + if (args.projectIds !== undefined) { + where.projectId = args.projectIds; + } + + if (args.actorId !== undefined) { + const actor = await db.Actor.findOne({ where: { publicId: args.actorId } }); + if (!actor) { + return { data: [], total: 0, limit, offset }; + } + const messages = await db.ConversationMessage.findAll({ + where: { actorId: actor.id }, + attributes: ['conversationId'], + group: ['conversationId'], + }); + const conversationIds = messages.map( + (m: InstanceType<(typeof db)['ConversationMessage']>) => { + return m.conversationId; + } + ); + where.id = conversationIds; + } + + const { count, rows } = await db.Conversation.findAndCountAll({ + where: Object.keys(where).length > 0 ? where : undefined, + include: [{ model: db.Project, as: 'project' }], + limit, + offset, + }); + + return { data: rows.map(mapConversation), total: count, limit, offset }; +}; + +export const getConversation = async (args: { id: string }) => { + const conversation = await db.Conversation.findOne({ + where: { publicId: args.id }, + include: [{ model: db.Project, as: 'project' }], + }); + + if (!conversation) { + return null; + } + + return mapConversation(conversation); +}; + +export const createConversation = async (args: { + projectId: number; + status?: string; +}) => { + const conversation = await db.Conversation.create({ + projectId: args.projectId, + status: args.status ?? 'open', + }); + + const conversationWithAssociations = await db.Conversation.findOne({ + where: { id: conversation.id }, + include: [{ model: db.Project, as: 'project' }], + }); + + return mapConversation(conversationWithAssociations!); +}; + +export const updateConversationStatus = async (args: { + id: string; + status: string; +}) => { + const conversation = await db.Conversation.findOne({ + where: { publicId: args.id }, + }); + + if (!conversation) { + return null; + } + + await conversation.update({ status: args.status }); + + const updatedConversation = await db.Conversation.findOne({ + where: { publicId: args.id }, + include: [{ model: db.Project, as: 'project' }], + }); + + return mapConversation(updatedConversation!); +}; + +export const deleteConversation = async (args: { id: string }) => { + const conversation = await db.Conversation.findOne({ + where: { publicId: args.id }, + }); + + if (!conversation) { + return null; + } + + await conversation.destroy(); + + return { id: args.id }; +}; + +export const getConversationTags = async (args: { id: string }) => { + const conversation = await db.Conversation.findOne({ + where: { publicId: args.id }, + }); + + if (!conversation) { + return null; + } + + return conversation.tags ?? {}; +}; + +export const updateConversationTags = async (args: { + id: string; + tags: Record; + merge?: boolean; +}) => { + const conversation = await db.Conversation.findOne({ + where: { publicId: args.id }, + }); + + if (!conversation) { + return null; + } + + const newTags = args.merge + ? { ...(conversation.tags ?? {}), ...args.tags } + : args.tags; + await conversation.update({ tags: newTags }); + + const updated = await db.Conversation.findOne({ + where: { publicId: args.id }, + include: [{ model: db.Project, as: 'project' }], + }); + + return mapConversation(updated!); +}; + +export const listConversationMessages = async (args: { + conversationId: string; + limit?: number; + offset?: number; +}) => { + const limit = args.limit ?? 50; + const offset = args.offset ?? 0; + + const conversation = await db.Conversation.findOne({ + where: { publicId: args.conversationId }, + }); + + if (!conversation) { + return null; + } + + const { count, rows } = await db.ConversationMessage.findAndCountAll({ + where: { conversationId: conversation.id }, + include: [ + { + model: db.Document, + as: 'document', + include: [{ model: db.File, as: 'file' }], + }, + { model: db.Actor, as: 'actor' }, + ], + order: [['position', 'ASC']], + limit, + offset, + }); + + return { data: rows.map(mapMessage), total: count, limit, offset }; +}; + +export const addConversationMessage = async (args: { + conversationId: string; + message: string; + actorId: string; + position?: number; +}) => { + const conversation = await db.Conversation.findOne({ + where: { publicId: args.conversationId }, + }); + + if (!conversation) { + return null; + } + + const actor = await db.Actor.findOne({ where: { publicId: args.actorId } }); + + if (!actor) { + return null; + } + + const createdDoc = await createDocument({ + projectId: conversation.projectId, + content: args.message, + }); + + const document = await db.Document.findOne({ + where: { publicId: createdDoc.id }, + }); + + if (!document) { + return null; + } + + let position = args.position; + + if (position === undefined) { + const maxMessage = await db.ConversationMessage.findOne({ + where: { conversationId: conversation.id }, + order: [['position', 'DESC']], + }); + position = maxMessage ? maxMessage.position + 1 : 0; + } + + const message = await db.ConversationMessage.create({ + conversationId: conversation.id, + documentId: document.id, + actorId: actor.id, + position, + }); + + const messageWithAssociations = await db.ConversationMessage.findOne({ + where: { id: message.id }, + include: [ + { + model: db.Document, + as: 'document', + include: [{ model: db.File, as: 'file' }], + }, + { model: db.Actor, as: 'actor' }, + ], + }); + + return mapMessage(messageWithAssociations!); +}; + +export const removeConversationMessage = async (args: { + conversationId: string; + documentId: string; +}) => { + const conversation = await db.Conversation.findOne({ + where: { publicId: args.conversationId }, + }); + + if (!conversation) { + return null; + } + + const document = await db.Document.findOne({ + where: { publicId: args.documentId }, + }); + + if (!document) { + return null; + } + + const message = await db.ConversationMessage.findOne({ + where: { + conversationId: conversation.id, + documentId: document.id, + }, + }); + + if (!message) { + return null; + } + + await message.destroy(); + + // Also delete the associated document to avoid orphans (Bug #3) + if (document.publicId) { + await deleteDocument({ id: document.publicId }); + } + + return { conversationId: args.conversationId, documentId: args.documentId }; +}; + +export const listConversationActors = async (args: { + conversationId: string; +}) => { + const conversation = await db.Conversation.findOne({ + where: { publicId: args.conversationId }, + }); + + if (!conversation) { + return null; + } + + const messages = await db.ConversationMessage.findAll({ + where: { conversationId: conversation.id }, + include: [ + { + model: db.Actor, + as: 'actor', + include: [{ model: db.Project, as: 'project' }], + }, + ], + }); + + const seen = new Set(); + const actors = []; + for (const msg of messages) { + if (!seen.has(msg.actorId)) { + seen.add(msg.actorId); + actors.push(msg.actor); + } + } + + return actors.map((actor) => { + return { + id: actor.publicId, + projectId: actor.project?.publicId, + name: actor.name, + type: actor.type ?? undefined, + externalId: actor.externalId ?? undefined, + createdAt: actor.createdAt, + updatedAt: actor.updatedAt, + }; + }); +}; diff --git a/packages/server/src/lib/documents.ts b/packages/server/src/lib/documents.ts new file mode 100644 index 00000000..1cb38a17 --- /dev/null +++ b/packages/server/src/lib/documents.ts @@ -0,0 +1,351 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +import { Op } from '@ttoss/postgresdb'; + +import { db } from '../db'; +import { getEmbedding } from './embedding'; + +const getStorageDir = () => { + const dir = process.env.FILES_STORAGE_DIR; + if (!dir) { + throw new Error('FILES_STORAGE_DIR environment variable is not set'); + } + return dir; +}; + +const mapDocument = ( + doc: InstanceType<(typeof db)['Document']> & { + file?: InstanceType<(typeof db)['File']> & { + project?: InstanceType<(typeof db)['Project']>; + }; + } +) => { + return { + id: doc.publicId, + fileId: doc.file?.publicId, + projectId: doc.file?.project?.publicId, + filename: doc.file?.filename, + size: doc.file?.size, + title: doc.title ?? undefined, + metadata: doc.metadata + ? (() => { + try { + return JSON.parse(doc.metadata!); + } catch { + return doc.metadata; + } + })() + : undefined, + tags: doc.tags ?? undefined, + createdAt: doc.createdAt, + updatedAt: doc.updatedAt, + }; +}; + +export const listDocuments = async (args: { + projectIds?: number[]; + limit?: number; + offset?: number; +}) => { + const limit = args.limit ?? 50; + const offset = args.offset ?? 0; + + if (args.projectIds !== undefined && args.projectIds.length === 0) { + return { data: [], total: 0, limit, offset }; + } + + const fileWhere = + args.projectIds !== undefined ? { projectId: args.projectIds } : undefined; + + const { count, rows } = await db.Document.findAndCountAll({ + distinct: true, + include: [ + { + model: db.File, + as: 'file', + where: fileWhere, + include: [{ model: db.Project, as: 'project' }], + }, + ], + limit, + offset, + }); + return { data: rows.map(mapDocument), total: count, limit, offset }; +}; + +export const getDocument = async (args: { id: string }) => { + const doc = await db.Document.findOne({ + where: { publicId: args.id }, + include: [ + { + model: db.File, + as: 'file', + include: [{ model: db.Project, as: 'project' }], + }, + ], + }); + + if (!doc) { + return null; + } + + const mapped = mapDocument(doc); + + if (doc.file?.storagePath && fs.existsSync(doc.file.storagePath)) { + const content = fs.readFileSync(doc.file.storagePath, 'utf-8'); + return { ...mapped, content }; + } + + return { ...mapped, content: null }; +}; + +export const createDocument = async (args: { + projectId: number; + content: string; + filename?: string; + title?: string; + metadata?: Record; + tags?: Record; +}) => { + const storageDir = getStorageDir(); + fs.mkdirSync(storageDir, { recursive: true }); + + const file = await db.File.create({ + projectId: args.projectId, + filename: args.filename ?? 'document.txt', + contentType: 'text/plain', + size: Buffer.byteLength(args.content, 'utf-8'), + storageType: 'local' as const, + storagePath: '', + }); + + const storagePath = path.join(storageDir, `${file.publicId}.txt`); + fs.writeFileSync(storagePath, args.content, 'utf-8'); + await file.update({ + storagePath, + size: Buffer.byteLength(args.content, 'utf-8'), + }); + + const embedding = await getEmbedding({ text: args.content }); + + const doc = await db.Document.create({ + fileId: file.id, + embedding, + title: args.title ?? null, + metadata: args.metadata ? JSON.stringify(args.metadata) : null, + tags: args.tags ?? null, + }); + + const created = await db.Document.findOne({ + where: { id: doc.id }, + include: [ + { + model: db.File, + as: 'file', + include: [{ model: db.Project, as: 'project' }], + }, + ], + }); + + return mapDocument(created!); +}; + +export const deleteDocument = async (args: { id: string }) => { + const doc = await db.Document.findOne({ + where: { publicId: args.id }, + include: [{ model: db.File, as: 'file' }], + }); + + if (!doc) { + return null; + } + + if (doc.file?.storagePath) { + try { + fs.unlinkSync(doc.file.storagePath); + } catch { + // Ignore missing file errors + } + } + + await doc.destroy(); + if (doc.file) { + await doc.file.destroy(); + } + + return true; +}; + +export const updateDocument = async (args: { + id: string; + content?: string; + title?: string; + metadata?: Record; + tags?: Record; +}) => { + const doc = await db.Document.findOne({ + where: { publicId: args.id }, + include: [ + { + model: db.File, + as: 'file', + include: [{ model: db.Project, as: 'project' }], + }, + ], + }); + + if (!doc) { + return null; + } + + if (args.content !== undefined && doc.file?.storagePath) { + fs.writeFileSync(doc.file.storagePath, args.content, 'utf-8'); + await doc.file.update({ + size: Buffer.byteLength(args.content, 'utf-8'), + }); + const embedding = await getEmbedding({ text: args.content }); + await doc.update({ embedding }); + } + + const updates: Record = {}; + if (args.title !== undefined) updates.title = args.title; + if (args.metadata !== undefined) + updates.metadata = JSON.stringify(args.metadata); + if (args.tags !== undefined) updates.tags = args.tags; + if (Object.keys(updates).length > 0) { + await doc.update(updates); + } + + const refreshed = await db.Document.findOne({ + where: { id: doc.id }, + include: [ + { + model: db.File, + as: 'file', + include: [{ model: db.Project, as: 'project' }], + }, + ], + }); + + return mapDocument(refreshed!); +}; + +export const searchDocuments = async (args: { + projectIds?: number[]; + query: string; + limit?: number; + threshold?: number; + tags?: Record; +}) => { + if (args.projectIds !== undefined && args.projectIds.length === 0) { + return []; + } + + const embedding = await getEmbedding({ text: args.query }); + const limit = args.limit ?? 10; + const embeddingLiteral = `[${embedding.join(',')}]`; + + const fileWhere = + args.projectIds !== undefined ? { projectId: args.projectIds } : undefined; + + const docWhere: Record = + args.tags && Object.keys(args.tags).length > 0 + ? { tags: { [Op.contains]: args.tags } } + : {}; + + const documents = await db.Document.findAll({ + where: docWhere, + attributes: { + include: [ + [ + db.Document.sequelize!.literal(`embedding <=> '${embeddingLiteral}'`), + 'distance', + ], + ], + }, + include: [ + { + model: db.File, + as: 'file', + where: fileWhere, + include: [{ model: db.Project, as: 'project' }], + }, + ], + order: db.Document.sequelize!.literal( + `embedding <=> '${embeddingLiteral}'` + ), + limit, + }); + + return documents + .map((doc: InstanceType<(typeof db)['Document']>) => { + const distance = parseFloat( + (doc.getDataValue('distance') as string) ?? '1' + ); + const score = 1 - distance; + const mapped = mapDocument(doc); + + let content: string | null = null; + if (doc.file?.storagePath && fs.existsSync(doc.file.storagePath)) { + content = fs.readFileSync(doc.file.storagePath, 'utf-8'); + } + + return { ...mapped, content, score }; + }) + .filter((doc: { score: number }) => { + if (args.threshold !== undefined) { + return doc.score >= args.threshold; + } + return true; + }); +}; + +export const getDocumentTags = async (args: { id: string }) => { + const doc = await db.Document.findOne({ where: { publicId: args.id } }); + + if (!doc) { + return null; + } + + return doc.tags ?? {}; +}; + +export const updateDocumentTags = async (args: { + id: string; + tags: Record; + merge?: boolean; +}) => { + const doc = await db.Document.findOne({ + where: { publicId: args.id }, + include: [ + { + model: db.File, + as: 'file', + include: [{ model: db.Project, as: 'project' }], + }, + ], + }); + + if (!doc) { + return null; + } + + const newTags = args.merge + ? { ...(doc.tags ?? {}), ...args.tags } + : args.tags; + await doc.update({ tags: newTags }); + + const refreshed = await db.Document.findOne({ + where: { id: doc.id }, + include: [ + { + model: db.File, + as: 'file', + include: [{ model: db.Project, as: 'project' }], + }, + ], + }); + + return mapDocument(refreshed!); +}; diff --git a/packages/server/src/lib/embedding.ts b/packages/server/src/lib/embedding.ts new file mode 100644 index 00000000..b5da90ab --- /dev/null +++ b/packages/server/src/lib/embedding.ts @@ -0,0 +1,23 @@ +import { Ollama } from 'ollama'; + +export const getEmbedding = async (args: { + text: string; +}): Promise => { + const provider = process.env.EMBEDDING_PROVIDER; + const model = process.env.EMBEDDING_MODEL; + + if (!provider || !model) { + throw new Error( + 'EMBEDDING_PROVIDER and EMBEDDING_MODEL environment variables must be set' + ); + } + + if (provider === 'ollama') { + const host = process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434'; + const ollama = new Ollama({ host }); + const response = await ollama.embed({ model, input: args.text }); + return response.embeddings[0]; + } + + throw new Error(`Unsupported embedding provider: ${provider}`); +}; diff --git a/packages/server/src/lib/files.ts b/packages/server/src/lib/files.ts new file mode 100644 index 00000000..4c9cb8bd --- /dev/null +++ b/packages/server/src/lib/files.ts @@ -0,0 +1,208 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +import { db } from '../db'; + +const getStorageDir = () => { + const dir = process.env.FILES_STORAGE_DIR; + if (!dir) { + throw new Error('FILES_STORAGE_DIR environment variable is not set'); + } + return dir; +}; + +const mapFile = (file: InstanceType<(typeof db)['File']>) => { + return { + id: file.publicId, + filename: file.filename, + contentType: file.contentType, + size: file.size, + storageType: file.storageType, + storagePath: file.storagePath, + metadata: file.metadata, + tags: file.tags ?? undefined, + createdAt: file.createdAt, + updatedAt: file.updatedAt, + }; +}; + +export const listFiles = async (args: { + projectIds?: number[]; + limit?: number; + offset?: number; +}) => { + const limit = args.limit ?? 50; + const offset = args.offset ?? 0; + + if (args.projectIds !== undefined && args.projectIds.length === 0) { + return { data: [], total: 0, limit, offset }; + } + + const where: Record = {}; + + if (args.projectIds !== undefined) { + where.projectId = args.projectIds; + } + + const { count, rows } = await db.File.findAndCountAll({ + where: Object.keys(where).length > 0 ? where : undefined, + limit, + offset, + }); + return { data: rows.map(mapFile), total: count, limit, offset }; +}; + +export const getFile = async (args: { id: string }) => { + const file = await db.File.findOne({ + where: { publicId: args.id }, + include: [{ model: db.Project, as: 'project' }], + }); + + if (!file) { + return null; + } + + return { + ...mapFile(file), + projectId: file.project?.publicId, + }; +}; + +export const uploadFile = async (args: { + projectId: number; + fileBuffer: Buffer; + filename?: string; + contentType?: string; + metadata?: string; +}) => { + const storageDir = getStorageDir(); + fs.mkdirSync(storageDir, { recursive: true }); + + // Create DB record first to get publicId for the filename + const file = await db.File.create({ + projectId: args.projectId, + filename: args.filename, + contentType: args.contentType, + size: args.fileBuffer.length, + storageType: 'local' as const, + storagePath: '', // filled in below after we know the publicId + metadata: args.metadata, + }); + + const ext = args.filename ? path.extname(args.filename) : ''; + const storagePath = path.join(storageDir, `${file.publicId}${ext}`); + fs.writeFileSync(storagePath, args.fileBuffer); + + await file.update({ storagePath, size: args.fileBuffer.length }); + + return mapFile(file); +}; + +export const downloadFile = async (args: { id: string }) => { + const file = await db.File.findOne({ where: { publicId: args.id } }); + + if (!file) { + return null; + } + + if (file.storageType !== 'local') { + throw new Error( + `Storage type '${file.storageType}' download not supported` + ); + } + + if (!fs.existsSync(file.storagePath)) { + return null; + } + + return { + stream: fs.createReadStream(file.storagePath), + filename: file.filename, + contentType: file.contentType, + size: file.size, + }; +}; + +export const updateFileMetadata = async (args: { + id: string; + metadata?: string; + filename?: string; +}) => { + const file = await db.File.findOne({ where: { publicId: args.id } }); + + if (!file) { + return null; + } + + const updates: Record = {}; + if (args.metadata !== undefined) { + updates.metadata = args.metadata; + } + if (args.filename !== undefined) { + updates.filename = args.filename; + } + + await file.update(updates); + return mapFile(file); +}; + +export const createFile = async (args: { + projectId: number; + filename?: string; + contentType?: string; + size?: number; + storageType: 'local' | 's3' | 'gcs'; + storagePath: string; + metadata?: string; +}) => { + const file = await db.File.create(args); + return mapFile(file); +}; + +export const deleteFile = async (args: { id: string }) => { + const file = await db.File.findOne({ where: { publicId: args.id } }); + + if (!file) { + return null; + } + + if (file.storageType === 'local' && file.storagePath) { + try { + fs.unlinkSync(file.storagePath); + } catch { + // Ignore missing file errors — record may still need to be cleaned up + } + } + + await file.destroy(); + return true; +}; + +export const getFileTags = async (args: { id: string }) => { + const file = await db.File.findOne({ where: { publicId: args.id } }); + + if (!file) { + return null; + } + + return file.tags ?? {}; +}; + +export const updateFileTags = async (args: { + id: string; + tags: Record; + merge?: boolean; +}) => { + const file = await db.File.findOne({ where: { publicId: args.id } }); + + if (!file) { + return null; + } + + const newTags = args.merge + ? { ...(file.tags ?? {}), ...args.tags } + : args.tags; + await file.update({ tags: newTags }); + + return { ...mapFile(file), tags: newTags }; +}; diff --git a/packages/server/src/lib/iam.ts b/packages/server/src/lib/iam.ts new file mode 100644 index 00000000..e85a3d0b --- /dev/null +++ b/packages/server/src/lib/iam.ts @@ -0,0 +1,256 @@ +export type Effect = 'Allow' | 'Deny'; + +export type ConditionOperator = + | 'StringEquals' + | 'StringNotEquals' + | 'StringLike'; + +export type Condition = { + [operator in ConditionOperator]?: Record; +}; + +export type Statement = { + effect: Effect; + action: string[]; + resource?: string[]; + condition?: Condition; +}; + +export type PolicyDocument = { + statement: Statement[]; +}; + +const VALID_EFFECTS: Effect[] = ['Allow', 'Deny']; +const VALID_OPERATORS: ConditionOperator[] = [ + 'StringEquals', + 'StringNotEquals', + 'StringLike', +]; + +const isValidAction = (action: string): boolean => { + if (action === '*') return true; + if (/^[a-zA-Z0-9_-]+:\*$/.test(action)) return true; + if (/^[a-zA-Z0-9_-]+:[a-zA-Z0-9_-]+$/.test(action)) return true; + return false; +}; + +const isValidSrnPattern = (srn: string): boolean => { + if (srn === '*') return true; + // soat::: + return /^soat:[^:]+:[^:]+:[^:]+$/.test(srn); +}; + +export const validatePolicyDocument = ( + doc: unknown +): { valid: boolean; errors: string[] } => { + const errors: string[] = []; + + if (!doc || typeof doc !== 'object' || Array.isArray(doc)) { + errors.push('Policy document must be an object'); + return { valid: false, errors }; + } + + const d = doc as Record; + + if (!Array.isArray(d.statement)) { + errors.push('Policy document must have a "statement" array'); + return { valid: false, errors }; + } + + for (let i = 0; i < d.statement.length; i++) { + const stmt = d.statement[i] as Record; + const prefix = `statement[${i}]`; + + if (!stmt || typeof stmt !== 'object' || Array.isArray(stmt)) { + errors.push(`${prefix}: must be an object`); + continue; + } + + // Validate effect + if (!VALID_EFFECTS.includes(stmt.effect as Effect)) { + errors.push( + `${prefix}.effect: must be one of ${VALID_EFFECTS.join(', ')}` + ); + } + + // Validate action + if (!Array.isArray(stmt.action) || stmt.action.length === 0) { + errors.push(`${prefix}.action: must be a non-empty array`); + } else { + for (const act of stmt.action) { + if (typeof act !== 'string' || !isValidAction(act)) { + errors.push( + `${prefix}.action: "${act}" is invalid — must be *, module:*, or module:Operation` + ); + } + } + } + + // Validate resource (optional) + if (stmt.resource !== undefined) { + if (!Array.isArray(stmt.resource) || stmt.resource.length === 0) { + errors.push( + `${prefix}.resource: must be a non-empty array when present` + ); + } else { + for (const res of stmt.resource) { + if (typeof res !== 'string' || !isValidSrnPattern(res)) { + errors.push( + `${prefix}.resource: "${res}" is invalid — must be * or soat:::` + ); + } + } + } + } + + // Validate condition (optional) + if (stmt.condition !== undefined) { + if ( + !stmt.condition || + typeof stmt.condition !== 'object' || + Array.isArray(stmt.condition) + ) { + errors.push(`${prefix}.condition: must be an object`); + } else { + const cond = stmt.condition as Record; + for (const op of Object.keys(cond)) { + if (!VALID_OPERATORS.includes(op as ConditionOperator)) { + errors.push( + `${prefix}.condition: "${op}" is not a valid operator — must be one of ${VALID_OPERATORS.join(', ')}` + ); + } else { + const block = cond[op] as Record; + if (!block || typeof block !== 'object' || Array.isArray(block)) { + errors.push(`${prefix}.condition.${op}: must be an object`); + } else { + for (const key of Object.keys(block)) { + if (!key.startsWith('soat:')) { + errors.push( + `${prefix}.condition.${op}: key "${key}" must start with "soat:"` + ); + } + } + } + } + } + } + } + } + + return { valid: errors.length === 0, errors }; +}; + +export const buildSrn = (args: { + projectPublicId: string; + resourceType: string; + resourceId: string; +}): string => { + return `soat:${args.projectPublicId}:${args.resourceType}:${args.resourceId}`; +}; + +export const matchesPattern = (args: { + pattern: string; + value: string; +}): boolean => { + const { pattern, value } = args; + + if (pattern === '*') return true; + if (pattern === value) return true; + + // module:* matches module:Anything + if (pattern.endsWith(':*')) { + const prefix = pattern.slice(0, -1); // e.g. "files:" + return value.startsWith(prefix); + } + + // Glob: * = any chars, ? = single char + const regexStr = pattern + .replace(/[.+^${}()|[\]\\]/g, '\\$&') // escape regex special chars + .replace(/\*/g, '.*') + .replace(/\?/g, '.'); + return new RegExp(`^${regexStr}$`).test(value); +}; + +export const evaluateCondition = (args: { + condition: Condition; + context: Record; +}): boolean => { + const { condition, context } = args; + + for (const op of Object.keys(condition) as ConditionOperator[]) { + const block = condition[op]; + if (!block) continue; + + for (const [key, expected] of Object.entries(block)) { + const actual = context[key]; + + if (op === 'StringEquals') { + if (actual !== expected) return false; + } else if (op === 'StringNotEquals') { + if (actual === expected) return false; + } else if (op === 'StringLike') { + if (!matchesPattern({ pattern: expected, value: actual ?? '' })) + return false; + } + } + } + + return true; +}; + +export const statementMatches = (args: { + statement: Statement; + action: string; + resource: string; + context: Record; +}): boolean => { + const { statement, action, resource, context } = args; + + // Check action + const actionMatch = statement.action.some((pattern) => { + return matchesPattern({ pattern, value: action }); + }); + if (!actionMatch) return false; + + // Check resource (omitted resource defaults to ["*"]) + const resources = statement.resource ?? ['*']; + const resourceMatch = resources.some((pattern) => { + return matchesPattern({ pattern, value: resource }); + }); + if (!resourceMatch) return false; + + // Check condition + if (statement.condition) { + if (!evaluateCondition({ condition: statement.condition, context })) + return false; + } + + return true; +}; + +export const evaluatePolicies = (args: { + policies: PolicyDocument[]; + action: string; + resource?: string; + context?: Record; +}): boolean => { + const resource = args.resource ?? '*'; + const context = args.context ?? {}; + + let allowed = false; + + for (const policy of args.policies) { + for (const statement of policy.statement) { + if ( + statementMatches({ statement, action: args.action, resource, context }) + ) { + if (statement.effect === 'Deny') { + return false; // Explicit deny, short-circuit + } + allowed = true; + } + } + } + + return allowed; +}; diff --git a/packages/server/src/lib/permissions.ts b/packages/server/src/lib/permissions.ts new file mode 100644 index 00000000..29173934 --- /dev/null +++ b/packages/server/src/lib/permissions.ts @@ -0,0 +1,81 @@ +import type { DB } from '../db'; +import { evaluatePolicies, type PolicyDocument } from './iam'; + +export const createProjectKeyIsAllowed = (args: { + projectPublicId: string; + userPolicyIds: number[]; + projectKeyPolicyId: number; + db: DB; +}) => { + return async (reqArgs: { + projectPublicId: string; + action: string; + resource?: string; + context?: Record; + }): Promise => { + if (reqArgs.projectPublicId !== args.projectPublicId) return false; + + const [userPolicies, projectKeyPolicy] = await Promise.all([ + args.userPolicyIds.length > 0 + ? args.db.ProjectPolicy.findAll({ where: { id: args.userPolicyIds } }) + : Promise.resolve([]), + args.db.ProjectPolicy.findOne({ where: { id: args.projectKeyPolicyId } }), + ]); + + if (!projectKeyPolicy) return false; + + const userAllowed = evaluatePolicies({ + policies: userPolicies.map((p) => { + return p.document as PolicyDocument; + }), + action: reqArgs.action, + resource: reqArgs.resource, + context: reqArgs.context, + }); + + if (!userAllowed) return false; + + return evaluatePolicies({ + policies: [projectKeyPolicy.document as PolicyDocument], + action: reqArgs.action, + resource: reqArgs.resource, + context: reqArgs.context, + }); + }; +}; + +export const createJwtIsAllowed = (args: { + role: 'admin' | 'user'; + userId: number; + db: DB; +}) => { + return async (reqArgs: { + projectPublicId: string; + action: string; + resource?: string; + context?: Record; + }): Promise => { + if (args.role === 'admin') return true; + const project = await args.db.Project.findOne({ + where: { publicId: reqArgs.projectPublicId }, + }); + if (!project) return false; + const membership = await args.db.UserProject.findOne({ + where: { userId: args.userId, projectId: project.id as number }, + }); + if (!membership) return false; + const policyIds = membership.policyIds as number[]; + if (policyIds.length === 0) return false; + const policies = await args.db.ProjectPolicy.findAll({ + where: { id: policyIds }, + }); + return evaluatePolicies({ + policies: policies.map((p) => { + return p.document as PolicyDocument; + }), + action: reqArgs.action, + resource: reqArgs.resource, + context: reqArgs.context, + }); + }; +}; diff --git a/packages/server/src/lib/projectKeys.ts b/packages/server/src/lib/projectKeys.ts new file mode 100644 index 00000000..40c12048 --- /dev/null +++ b/packages/server/src/lib/projectKeys.ts @@ -0,0 +1,78 @@ +import crypto from 'node:crypto'; + +import { PROJECT_KEY_RAW_PREFIX } from '@soat/postgresdb'; +import bcrypt from 'bcryptjs'; + +import { db } from '../db'; + +const mapProjectKey = (projectKey: InstanceType<(typeof db)['ProjectKey']>) => { + return { + id: projectKey.publicId, + name: projectKey.name, + keyPrefix: projectKey.keyPrefix, + createdAt: projectKey.createdAt, + updatedAt: projectKey.updatedAt, + }; +}; + +export const createProjectKey = async (args: { + userId: number; + projectId: number; + policyId: number; + name: string; +}) => { + const random = crypto.randomBytes(32).toString('hex'); + const key = `${PROJECT_KEY_RAW_PREFIX}${random}`; + const keyPrefix = key.slice(0, 8); + const keyHash = await bcrypt.hash(key, 10); + + const projectKey = await db.ProjectKey.create({ + ...args, + keyPrefix, + keyHash, + }); + + return { + ...mapProjectKey(projectKey), + key, // Return the full key only once at creation + }; +}; + +export const getProjectKey = async (args: { id: string }) => { + const projectKey = await db.ProjectKey.findOne({ + where: { publicId: args.id }, + include: [ + { model: db.User, as: 'user' }, + { model: db.Project, as: 'project' }, + { model: db.ProjectPolicy, as: 'policy' }, + ], + }); + + if (!projectKey) { + return null; + } + + return { + ...mapProjectKey(projectKey), + userId: projectKey.user?.publicId, + projectId: projectKey.project?.publicId, + policyId: projectKey.policy?.publicId, + }; +}; + +export const updateProjectKey = async (args: { + id: string; + policyId: number; +}) => { + const projectKey = await db.ProjectKey.findOne({ + where: { publicId: args.id }, + }); + + if (!projectKey) { + return null; + } + + await projectKey.update({ policyId: args.policyId }); + + return getProjectKey({ id: args.id }); +}; diff --git a/packages/server/src/lib/projects.ts b/packages/server/src/lib/projects.ts new file mode 100644 index 00000000..721da4e9 --- /dev/null +++ b/packages/server/src/lib/projects.ts @@ -0,0 +1,364 @@ +import type { AuthUser } from '../Context'; +import { db } from '../db'; +import type { PolicyDocument } from './iam'; +import { validatePolicyDocument } from './iam'; + +const mapProject = (project: InstanceType<(typeof db)['Project']>) => { + return { + id: project.publicId, + name: project.name, + createdAt: project.createdAt, + updatedAt: project.updatedAt, + }; +}; + +export const listProjects = async (args: { authUser: AuthUser }) => { + if (args.authUser.role === 'admin') { + const projects = await db.Project.findAll(); + return projects.map(mapProject); + } + + if (args.authUser.projectKeyProjectId) { + const project = await db.Project.findOne({ + where: { publicId: args.authUser.projectKeyProjectId }, + }); + return project ? [mapProject(project)] : []; + } + + const userProjects = await db.UserProject.findAll({ + where: { userId: args.authUser.id }, + include: [{ model: db.Project }], + }); + + return userProjects.map((up: InstanceType<(typeof db)['UserProject']>) => { + return mapProject(up.project as InstanceType<(typeof db)['Project']>); + }); +}; + +export const getProject = async (args: { id: string; authUser: AuthUser }) => { + const project = await db.Project.findOne({ where: { publicId: args.id } }); + + if (!project) { + return 'not_found' as const; + } + + if (args.authUser.role === 'admin') { + return mapProject(project); + } + + const membership = await db.UserProject.findOne({ + where: { userId: args.authUser.id, projectId: project.id }, + }); + + if (!membership) { + return 'forbidden' as const; + } + + return mapProject(project); +}; + +export const createProject = async (args: { name: string }) => { + const project = await db.Project.create({ name: args.name }); + return mapProject(project); +}; + +export const deleteProject = async (args: { id: string }) => { + const project = await db.Project.findOne({ where: { publicId: args.id } }); + + if (!project) { + return null; + } + + await project.destroy(); + return true; +}; + +const mapPolicy = ( + policy: InstanceType<(typeof db)['ProjectPolicy']>, + projectPublicId: string +) => { + const doc = policy.document as PolicyDocument | undefined; + const permissions = + doc?.statement + ?.filter((s) => { + return s.effect === 'Allow'; + }) + .flatMap((s) => { + return s.action; + }) ?? []; + const notPermissions = + doc?.statement + ?.filter((s) => { + return s.effect === 'Deny'; + }) + .flatMap((s) => { + return s.action; + }) ?? []; + return { + id: policy.publicId, + name: policy.name, + description: policy.description, + permissions, + notPermissions, + projectId: projectPublicId, + createdAt: policy.createdAt, + updatedAt: policy.updatedAt, + }; +}; + +export const listProjectPolicies = async (args: { projectId: string }) => { + const project = await db.Project.findOne({ + where: { publicId: args.projectId }, + }); + if (!project) { + return []; + } + + const policies = await db.ProjectPolicy.findAll({ + where: { projectId: project.id }, + }); + + return policies.map((policy: InstanceType<(typeof db)['ProjectPolicy']>) => { + return mapPolicy(policy, project.publicId); + }); +}; + +export const createProjectPolicy = async (args: { + projectId: string; + name?: string; + description?: string; + document: PolicyDocument; +}): Promise< + | ReturnType + | 'not_found' + | { invalid: true; errors: string[] } +> => { + const validation = validatePolicyDocument(args.document); + if (!validation.valid) { + return { invalid: true, errors: validation.errors }; + } + + const project = await db.Project.findOne({ + where: { publicId: args.projectId }, + }); + if (!project) { + return 'not_found'; + } + + const policy = await db.ProjectPolicy.create({ + projectId: project.id, + name: args.name ?? null, + description: args.description ?? null, + document: args.document as object, + }); + + return mapPolicy(policy, project.publicId); +}; + +export const updateProjectPolicy = async (args: { + projectId: string; + policyId: string; + name?: string; + description?: string; + document: PolicyDocument; +}): Promise< + | ReturnType + | 'not_found' + | { invalid: true; errors: string[] } +> => { + const validation = validatePolicyDocument(args.document); + if (!validation.valid) { + return { invalid: true, errors: validation.errors }; + } + + const project = await db.Project.findOne({ + where: { publicId: args.projectId }, + }); + if (!project) { + return 'not_found'; + } + + const policy = await db.ProjectPolicy.findOne({ + where: { publicId: args.policyId, projectId: project.id }, + }); + if (!policy) { + return 'not_found'; + } + + await policy.update({ + name: args.name ?? policy.name, + description: args.description ?? policy.description, + document: args.document as object, + }); + + return mapPolicy(policy, project.publicId); +}; + +export const deleteProjectPolicy = async (args: { + projectId: string; + policyId: string; +}): Promise<'not_found' | true> => { + const project = await db.Project.findOne({ + where: { publicId: args.projectId }, + }); + if (!project) { + return 'not_found'; + } + + const policy = await db.ProjectPolicy.findOne({ + where: { publicId: args.policyId, projectId: project.id }, + }); + if (!policy) { + return 'not_found'; + } + + await policy.destroy(); + return true; +}; + +export const getProjectPolicy = async (args: { + projectId: string; + policyId: string; +}) => { + const project = await db.Project.findOne({ + where: { publicId: args.projectId }, + }); + if (!project) { + return null; + } + + const policy = await db.ProjectPolicy.findOne({ + where: { publicId: args.policyId, projectId: project.id }, + }); + if (!policy) { + return null; + } + + return mapPolicy(policy, project.publicId); +}; + +export const addUserToProject = async (args: { + projectId: string; + userId: string; + policyIds?: string[]; +}) => { + const project = await db.Project.findOne({ + where: { publicId: args.projectId }, + }); + if (!project) { + return null; + } + + const user = await db.User.findOne({ where: { publicId: args.userId } }); + if (!user) { + return null; + } + + let resolvedPolicyIds: number[] = []; + if (args.policyIds && args.policyIds.length > 0) { + const policies = await db.ProjectPolicy.findAll({ + where: { publicId: args.policyIds, projectId: project.id }, + }); + if (policies.length !== args.policyIds.length) { + return null; + } + resolvedPolicyIds = policies.map( + (p: InstanceType<(typeof db)['ProjectPolicy']>) => { + return p.id as number; + } + ); + } + + // Check if membership already exists + const existing = await db.UserProject.findOne({ + where: { userId: user.id, projectId: project.id }, + }); + + if (existing) { + await existing.update({ policyIds: resolvedPolicyIds }); + return true; + } + + await db.UserProject.create({ + userId: user.id, + projectId: project.id, + policyIds: resolvedPolicyIds, + }); + + return true; +}; + +export const updateUserProjectPolicies = async (args: { + projectId: string; + userId: string; + policyIds: string[]; +}) => { + const project = await db.Project.findOne({ + where: { publicId: args.projectId }, + }); + if (!project) { + return 'not_found' as const; + } + + const user = await db.User.findOne({ where: { publicId: args.userId } }); + if (!user) { + return 'not_found' as const; + } + + const membership = await db.UserProject.findOne({ + where: { userId: user.id, projectId: project.id }, + }); + if (!membership) { + return 'not_found' as const; + } + + const policies = await db.ProjectPolicy.findAll({ + where: { publicId: args.policyIds, projectId: project.id }, + }); + if (policies.length !== args.policyIds.length) { + return 'not_found' as const; + } + + await membership.update({ + policyIds: policies.map((p: InstanceType<(typeof db)['ProjectPolicy']>) => { + return p.id as number; + }), + }); + return true; +}; + +export const getUserProjectPolicies = async (args: { + projectId: string; + userId: string; +}) => { + const project = await db.Project.findOne({ + where: { publicId: args.projectId }, + }); + if (!project) { + return null; + } + + const user = await db.User.findOne({ where: { publicId: args.userId } }); + if (!user) { + return null; + } + + const membership = await db.UserProject.findOne({ + where: { userId: user.id, projectId: project.id }, + }); + if (!membership) { + return null; + } + + if (!membership.policyIds || membership.policyIds.length === 0) { + return []; + } + + const policies = await db.ProjectPolicy.findAll({ + where: { id: membership.policyIds, projectId: project.id }, + }); + + return policies.map((p: InstanceType<(typeof db)['ProjectPolicy']>) => { + return mapPolicy(p, project.publicId); + }); +}; diff --git a/packages/server/src/lib/secrets.ts b/packages/server/src/lib/secrets.ts new file mode 100644 index 00000000..5a2df462 --- /dev/null +++ b/packages/server/src/lib/secrets.ts @@ -0,0 +1,136 @@ +import crypto from 'node:crypto'; + +import { db } from 'src/db'; + +const ALGORITHM = 'aes-256-gcm'; +const IV_LENGTH = 12; +const AUTH_TAG_LENGTH = 16; + +const getEncryptionKey = () => { + const key = process.env.SECRETS_ENCRYPTION_KEY; + if (!key) { + throw new Error('SECRETS_ENCRYPTION_KEY environment variable is not set'); + } + const buf = Buffer.from(key, 'hex'); + if (buf.length !== 32) { + throw new Error( + 'SECRETS_ENCRYPTION_KEY must be a 64-character hex string (32 bytes)' + ); + } + return buf; +}; + +export const encryptValue = (plaintext: string): string => { + const key = getEncryptionKey(); + const iv = crypto.randomBytes(IV_LENGTH); + const cipher = crypto.createCipheriv(ALGORITHM, key, iv, { + authTagLength: AUTH_TAG_LENGTH, + }); + const encrypted = Buffer.concat([ + cipher.update(plaintext, 'utf8'), + cipher.final(), + ]); + const authTag = cipher.getAuthTag(); + return Buffer.concat([iv, authTag, encrypted]).toString('base64'); +}; + +export const decryptValue = (ciphertext: string): string => { + const key = getEncryptionKey(); + const buf = Buffer.from(ciphertext, 'base64'); + const iv = buf.subarray(0, IV_LENGTH); + const authTag = buf.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH); + const encrypted = buf.subarray(IV_LENGTH + AUTH_TAG_LENGTH); + const decipher = crypto.createDecipheriv(ALGORITHM, key, iv, { + authTagLength: AUTH_TAG_LENGTH, + }); + decipher.setAuthTag(authTag); + return decipher.update(encrypted) + decipher.final('utf8'); +}; + +const mapSecret = ( + instance: InstanceType<(typeof db)['Secret']> & { + project?: InstanceType<(typeof db)['Project']>; + } +) => ({ + id: instance.publicId, + projectId: instance.project?.publicId, + name: instance.name, + hasValue: instance.encryptedValue !== null, + createdAt: instance.createdAt, + updatedAt: instance.updatedAt, +}); + +export const listSecrets = async (args: { projectIds: number[] }) => { + const secrets = await db.Secret.findAll({ + where: { projectId: args.projectIds }, + include: [{ model: db.Project, as: 'project' }], + }); + return secrets.map(mapSecret); +}; + +export const getSecret = async (args: { id: string }) => { + const secret = await db.Secret.findOne({ + where: { publicId: args.id }, + include: [{ model: db.Project, as: 'project' }], + }); + if (!secret) return null; + return mapSecret(secret); +}; + +export const createSecret = async (args: { + projectId: number; + name: string; + value?: string; +}) => { + const secret = await db.Secret.create({ + projectId: args.projectId, + name: args.name, + encryptedValue: args.value ? encryptValue(args.value) : null, + }); + const withProject = await db.Secret.findOne({ + where: { id: secret.id }, + include: [{ model: db.Project, as: 'project' }], + }); + return mapSecret(withProject!); +}; + +export const updateSecret = async (args: { + id: string; + name?: string; + value?: string; +}) => { + const secret = await db.Secret.findOne({ + where: { publicId: args.id }, + include: [{ model: db.Project, as: 'project' }], + }); + if (!secret) return null; + + if (args.name !== undefined) { + secret.name = args.name; + } + if (args.value !== undefined) { + secret.encryptedValue = encryptValue(args.value); + } + await secret.save(); + return mapSecret(secret); +}; + +export const deleteSecret = async (args: { id: string; force?: boolean }) => { + const secret = await db.Secret.findOne({ where: { publicId: args.id } }); + if (!secret) return null; + + const dependentCount = await db.AiProvider.count({ + where: { secretId: secret.id }, + }); + + if (dependentCount > 0 && !args.force) { + return 'conflict' as const; + } + + if (args.force) { + await db.AiProvider.destroy({ where: { secretId: secret.id } }); + } + + await secret.destroy(); + return 'deleted' as const; +}; diff --git a/packages/server/src/lib/users.ts b/packages/server/src/lib/users.ts new file mode 100644 index 00000000..0c7edbb8 --- /dev/null +++ b/packages/server/src/lib/users.ts @@ -0,0 +1,104 @@ +import { db } from '../db'; +import { + comparePassword, + hashPassword, + signUserToken, +} from '../middleware/auth'; + +const mapUser = (user: InstanceType<(typeof db)['User']>) => { + return { + id: user.publicId, + username: user.username, + role: user.role, + createdAt: user.createdAt, + updatedAt: user.updatedAt, + }; +}; + +export const listUsers = async () => { + const allUsers = await db.User.findAll(); + return allUsers.map(mapUser); +}; + +export const getUser = async (args: { id: string }) => { + const user = await db.User.findOne({ where: { publicId: args.id } }); + + if (!user) { + return null; + } + + return mapUser(user); +}; + +export const loginUser = async (args: { + username: string; + password: string; +}) => { + const user = await db.User.findOne({ where: { username: args.username } }); + + if (!user) { + return null; + } + + const valid = await comparePassword( + args.password, + user.passwordHash as string + ); + + if (!valid) { + return null; + } + + const token = signUserToken({ + publicId: user.publicId as string, + role: user.role as string, + }); + + return { ...mapUser(user), token }; +}; + +export const createUser = async (args: { + username: string; + password: string; + role?: 'admin' | 'user'; +}) => { + const passwordHash = await hashPassword(args.password); + const user = await db.User.create({ + username: args.username, + passwordHash, + role: args.role ?? 'user', + }); + + return mapUser(user); +}; + +export const createFirstAdminUser = async (args: { + username: string; + password: string; +}) => { + const count = await db.User.count(); + + if (count > 0) { + return null; + } + + const passwordHash = await hashPassword(args.password); + const user = await db.User.create({ + username: args.username, + passwordHash, + role: 'admin', + }); + + return mapUser(user); +}; + +export const deleteUser = async (args: { id: string }) => { + const user = await db.User.findOne({ where: { publicId: args.id } }); + + if (!user) { + return false; + } + + await user.destroy(); + return true; +}; diff --git a/packages/server/src/mcp/index.ts b/packages/server/src/mcp/index.ts deleted file mode 100644 index 7b91cca8..00000000 --- a/packages/server/src/mcp/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { mcpRouter } from './server'; diff --git a/packages/server/src/mcp/prompts/index.ts b/packages/server/src/mcp/prompts/index.ts deleted file mode 100644 index 8e19d816..00000000 --- a/packages/server/src/mcp/prompts/index.ts +++ /dev/null @@ -1 +0,0 @@ -// MCP prompts will be defined here diff --git a/packages/server/src/mcp/resources/index.ts b/packages/server/src/mcp/resources/index.ts deleted file mode 100644 index 29a85229..00000000 --- a/packages/server/src/mcp/resources/index.ts +++ /dev/null @@ -1 +0,0 @@ -// MCP resources will be defined here diff --git a/packages/server/src/mcp/server.ts b/packages/server/src/mcp/server.ts index 8bfdc788..6fd058af 100644 --- a/packages/server/src/mcp/server.ts +++ b/packages/server/src/mcp/server.ts @@ -1,74 +1,22 @@ -import { createMcpRouter, Server as McpServer } from '@ttoss/http-server-mcp'; +import { createMcpRouter, McpServer } from '@ttoss/http-server-mcp'; -import pkg from '../../package.json' with { type: 'json' }; -import { - createDocumentTool, - deleteDocumentTool, - getDocumentTool, - listDocumentsTool, - searchDocumentsTool, - updateDocumentTool, -} from './tools'; +import { registerTools } from './tools/index'; const mcpServer = new McpServer({ - name: 'soat-server', - version: pkg.version, + name: 'soat', + version: '1.0.0', }); -mcpServer.registerTool( - listDocumentsTool.name, - { - description: listDocumentsTool.description, - inputSchema: listDocumentsTool.inputSchema, - }, - listDocumentsTool.handler -); - -mcpServer.registerTool( - createDocumentTool.name, - { - description: createDocumentTool.description, - inputSchema: createDocumentTool.inputSchema, - }, - createDocumentTool.handler -); +registerTools(mcpServer); -mcpServer.registerTool( - getDocumentTool.name, - { - description: getDocumentTool.description, - inputSchema: getDocumentTool.inputSchema, +const mcpRouter = createMcpRouter(mcpServer, { + // eslint-disable-next-line turbo/no-undeclared-env-vars + apiBaseUrl: `http://localhost:${process.env.PORT || 5047}/api/v1`, + getApiHeaders: (ctx) => { + return { + authorization: (ctx.headers.authorization as string) ?? '', + }; }, - getDocumentTool.handler -); - -mcpServer.registerTool( - updateDocumentTool.name, - { - description: updateDocumentTool.description, - inputSchema: updateDocumentTool.inputSchema, - }, - updateDocumentTool.handler -); - -mcpServer.registerTool( - deleteDocumentTool.name, - { - description: deleteDocumentTool.description, - inputSchema: deleteDocumentTool.inputSchema, - }, - deleteDocumentTool.handler -); - -mcpServer.registerTool( - searchDocumentsTool.name, - { - description: searchDocumentsTool.description, - inputSchema: searchDocumentsTool.inputSchema, - }, - searchDocumentsTool.handler -); - -export const mcpRouter = createMcpRouter(mcpServer, { - path: '/mcp', }); + +export { mcpRouter }; diff --git a/packages/server/src/mcp/tools/actors.ts b/packages/server/src/mcp/tools/actors.ts new file mode 100644 index 00000000..d08dc223 --- /dev/null +++ b/packages/server/src/mcp/tools/actors.ts @@ -0,0 +1,165 @@ +import type { McpServer } from '@ttoss/http-server-mcp'; +import { apiCall, z } from '@ttoss/http-server-mcp'; + +const registerTools = (server: McpServer) => { + server.registerTool( + 'list-actors', + { + description: + 'List actors. If projectId is omitted, returns all actors accessible to the caller. Optionally filter by externalId, name (partial match), or type (exact match).', + inputSchema: { + projectId: z.string().optional().describe('Project ID (optional)'), + externalId: z + .string() + .optional() + .describe('External ID to filter by (e.g. WhatsApp phone number)'), + name: z + .string() + .optional() + .describe('Partial, case-insensitive name filter'), + type: z + .string() + .optional() + .describe('Exact type filter (e.g. customer, agent)'), + limit: z + .number() + .optional() + .describe('Maximum number of results to return (default 50)'), + offset: z + .number() + .optional() + .describe('Number of results to skip (default 0)'), + }, + }, + async ({ projectId, externalId, name, type, limit, offset }) => { + const params = new URLSearchParams(); + if (projectId) params.set('projectId', projectId); + if (externalId) params.set('externalId', externalId); + if (name) params.set('name', name); + if (type) params.set('type', type); + if (limit !== undefined) params.set('limit', String(limit)); + if (offset !== undefined) params.set('offset', String(offset)); + const qs = params.toString() ? `?${params.toString()}` : ''; + const data = await apiCall('GET', `/actors${qs}`); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'get-actor', + { + description: 'Get an actor by ID', + inputSchema: { + id: z.string().describe('Actor ID'), + }, + }, + async ({ id }) => { + try { + const data = await apiCall('GET', `/actors/${id}`); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } catch (error) { + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + error: 'not_found', + message: String(error), + }), + }, + ], + }; + } + } + ); + + server.registerTool( + 'create-actor', + { + description: + 'Create a new actor. project keys infer the project automatically; JWT callers must supply projectId.', + inputSchema: { + projectId: z + .string() + .optional() + .describe( + 'Project ID (required for JWT auth, optional for project keys)' + ), + name: z.string().describe('Actor name'), + type: z + .string() + .optional() + .describe("Optional actor type (e.g. 'customer', 'agent')"), + externalId: z + .string() + .optional() + .describe( + 'Optional external identifier (e.g. WhatsApp phone number). Must be unique within the project.' + ), + }, + }, + async ({ projectId, name, type, externalId }) => { + const data = await apiCall('POST', '/actors', { + body: { projectId, name, type, externalId }, + }); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'delete-actor', + { + description: 'Delete an actor by ID', + inputSchema: { + id: z.string().describe('Actor ID'), + }, + }, + async ({ id }) => { + try { + await apiCall('DELETE', `/actors/${id}`); + return { + content: [ + { + type: 'text', + text: JSON.stringify({ id, deleted: true }), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + id, + deleted: false, + error: String(error), + }), + }, + ], + }; + } + } + ); + + server.registerTool( + 'update-actor', + { + description: 'Update an actor by ID', + inputSchema: { + id: z.string().describe('Actor ID'), + name: z.string().optional().describe('New name'), + type: z.string().optional().describe('New type'), + externalId: z.string().optional().describe('New external ID'), + }, + }, + async ({ id, name, type, externalId }) => { + const data = await apiCall('PATCH', `/actors/${id}`, { + body: { name, type, externalId }, + }); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); +}; + +export { registerTools }; diff --git a/packages/server/src/mcp/tools/aiProviders.ts b/packages/server/src/mcp/tools/aiProviders.ts new file mode 100644 index 00000000..da392a98 --- /dev/null +++ b/packages/server/src/mcp/tools/aiProviders.ts @@ -0,0 +1,144 @@ +import type { McpServer } from '@ttoss/http-server-mcp'; +import { apiCall, z } from '@ttoss/http-server-mcp'; + +const AI_PROVIDER_SLUGS = [ + 'openai', + 'anthropic', + 'google', + 'xai', + 'groq', + 'ollama', + 'azure', + 'bedrock', + 'gateway', + 'custom', +] as const; + +const registerTools = (server: McpServer) => { + server.registerTool( + 'list-ai-providers', + { + description: 'List AI providers in a project.', + inputSchema: { + projectId: z.string().optional().describe('Project ID to filter by'), + }, + }, + async ({ projectId }) => { + const params = new URLSearchParams(); + if (projectId) params.set('projectId', projectId); + const qs = params.toString() ? `?${params.toString()}` : ''; + const data = await apiCall('GET', `/ai-providers${qs}`); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'get-ai-provider', + { + description: 'Get an AI provider by ID.', + inputSchema: { + id: z.string().describe('AI Provider ID'), + }, + }, + async ({ id }) => { + const data = await apiCall('GET', `/ai-providers/${id}`); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'create-ai-provider', + { + description: 'Create a new AI provider configuration.', + inputSchema: { + projectId: z + .string() + .optional() + .describe( + 'Project ID. Required for JWT auth; omit when using a project key.' + ), + secretId: z + .string() + .optional() + .describe('Secret ID containing the provider credentials'), + name: z.string().describe('Display name for this AI provider'), + provider: z.enum(AI_PROVIDER_SLUGS).describe('Provider type'), + defaultModel: z.string().describe('Default model to use'), + baseUrl: z + .string() + .optional() + .describe('Custom base URL for the provider API'), + config: z + .record(z.unknown()) + .optional() + .describe('Provider-specific configuration as a JSON object'), + }, + }, + async ({ + projectId, + secretId, + name, + provider, + defaultModel, + baseUrl, + config, + }) => { + const data = await apiCall('POST', '/ai-providers', { + body: { + projectId, + secretId, + name, + provider, + defaultModel, + baseUrl, + config, + }, + }); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'update-ai-provider', + { + description: 'Update an AI provider configuration.', + inputSchema: { + id: z.string().describe('AI Provider ID'), + secretId: z.string().optional().describe('New Secret ID'), + name: z.string().optional().describe('New display name'), + provider: z + .enum(AI_PROVIDER_SLUGS) + .optional() + .describe('New provider type'), + defaultModel: z.string().optional().describe('New default model'), + baseUrl: z.string().optional().describe('New base URL'), + config: z + .record(z.unknown()) + .optional() + .describe('New provider-specific configuration'), + }, + }, + async ({ id, secretId, name, provider, defaultModel, baseUrl, config }) => { + const data = await apiCall('PATCH', `/ai-providers/${id}`, { + body: { secretId, name, provider, defaultModel, baseUrl, config }, + }); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'delete-ai-provider', + { + description: 'Delete an AI provider.', + inputSchema: { + id: z.string().describe('AI Provider ID'), + }, + }, + async ({ id }) => { + const data = await apiCall('DELETE', `/ai-providers/${id}`); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); +}; + +export { registerTools }; diff --git a/packages/server/src/mcp/tools/conversations.ts b/packages/server/src/mcp/tools/conversations.ts new file mode 100644 index 00000000..3f72e486 --- /dev/null +++ b/packages/server/src/mcp/tools/conversations.ts @@ -0,0 +1,257 @@ +import type { McpServer } from '@ttoss/http-server-mcp'; +import { apiCall, z } from '@ttoss/http-server-mcp'; + +const registerTools = (server: McpServer) => { + server.registerTool( + 'list-conversations', + { + description: + 'List conversations. If projectId is omitted, returns all conversations accessible to the caller. Optionally filter by actorId.', + inputSchema: { + projectId: z.string().optional().describe('Project ID (optional)'), + actorId: z + .string() + .optional() + .describe('Actor ID to filter conversations by'), + limit: z + .number() + .optional() + .describe('Maximum number of results to return (default 50)'), + offset: z + .number() + .optional() + .describe('Number of results to skip (default 0)'), + }, + }, + async ({ projectId, actorId, limit, offset }) => { + const params = new URLSearchParams(); + if (projectId) params.set('projectId', projectId); + if (actorId) params.set('actorId', actorId); + if (limit !== undefined) params.set('limit', String(limit)); + if (offset !== undefined) params.set('offset', String(offset)); + const qs = params.toString() ? `?${params.toString()}` : ''; + const data = await apiCall('GET', `/conversations${qs}`); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'get-conversation', + { + description: 'Get a conversation by ID', + inputSchema: { + id: z.string().describe('Conversation ID'), + }, + }, + async ({ id }) => { + try { + const data = await apiCall('GET', `/conversations/${id}`); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } catch (error) { + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + error: 'not_found', + message: String(error), + }), + }, + ], + }; + } + } + ); + + server.registerTool( + 'create-conversation', + { + description: + 'Create a new conversation. project keys infer the project automatically; JWT callers must supply projectId.', + inputSchema: { + projectId: z + .string() + .optional() + .describe( + 'Project ID (required for JWT auth, optional for project keys)' + ), + status: z + .string() + .optional() + .describe( + "Initial status, either 'open' or 'closed'. Defaults to 'open'." + ), + }, + }, + async ({ projectId, status }) => { + const data = await apiCall('POST', '/conversations', { + body: { projectId, status }, + }); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'update-conversation', + { + description: "Update a conversation's status", + inputSchema: { + id: z.string().describe('Conversation ID'), + status: z.string().describe("New status, either 'open' or 'closed'"), + }, + }, + async ({ id, status }) => { + const data = await apiCall('PATCH', `/conversations/${id}`, { + body: { status }, + }); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'delete-conversation', + { + description: 'Delete a conversation by ID', + inputSchema: { + id: z.string().describe('Conversation ID'), + }, + }, + async ({ id }) => { + try { + await apiCall('DELETE', `/conversations/${id}`); + return { + content: [ + { + type: 'text', + text: JSON.stringify({ id, deleted: true }), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + id, + deleted: false, + error: String(error), + }), + }, + ], + }; + } + } + ); + + server.registerTool( + 'list-conversation-messages', + { + description: + 'List all messages (documents) in a conversation, ordered by position', + inputSchema: { + id: z.string().describe('Conversation ID'), + limit: z + .number() + .optional() + .describe('Maximum number of results to return (default 50)'), + offset: z + .number() + .optional() + .describe('Number of results to skip (default 0)'), + }, + }, + async ({ id, limit, offset }) => { + const params = new URLSearchParams(); + if (limit !== undefined) params.set('limit', String(limit)); + if (offset !== undefined) params.set('offset', String(offset)); + const qs = params.toString() ? `?${params.toString()}` : ''; + const data = await apiCall('GET', `/conversations/${id}/messages${qs}`); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'add-conversation-message', + { + description: + 'Add a message to a conversation. The message content is saved as a document internally. If position is omitted, the message is appended at the end.', + inputSchema: { + id: z.string().describe('Conversation ID'), + message: z.string().describe('Message text content to send'), + actorId: z.string().describe('Actor ID who is sending this message'), + position: z + .number() + .optional() + .describe( + 'Zero-based position in the conversation. Defaults to MAX+1 (append).' + ), + }, + }, + async ({ id, message, actorId, position }) => { + const data = await apiCall('POST', `/conversations/${id}/messages`, { + body: { message, actorId, position }, + }); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'list-conversation-actors', + { + description: + 'List all distinct actors who have sent at least one message in a conversation', + inputSchema: { + id: z.string().describe('Conversation ID'), + }, + }, + async ({ id }) => { + const data = await apiCall('GET', `/conversations/${id}/actors`); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'remove-conversation-message', + { + description: 'Remove a document from a conversation', + inputSchema: { + id: z.string().describe('Conversation ID'), + documentId: z.string().describe('Document ID to remove'), + }, + }, + async ({ id, documentId }) => { + try { + await apiCall('DELETE', `/conversations/${id}/messages/${documentId}`); + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + conversationId: id, + documentId, + deleted: true, + }), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + conversationId: id, + documentId, + deleted: false, + error: String(error), + }), + }, + ], + }; + } + } + ); +}; + +export { registerTools }; diff --git a/packages/server/src/mcp/tools/documents.ts b/packages/server/src/mcp/tools/documents.ts index 8ab21699..8ac2d640 100644 --- a/packages/server/src/mcp/tools/documents.ts +++ b/packages/server/src/mcp/tools/documents.ts @@ -1,428 +1,201 @@ -import { - createDocument, - deleteDocument, - type EmbeddingConfig, - getDocument, - listDocuments, - searchDocumentsBySimilarity, - type StorageConfig, - updateDocument, -} from '@soat/documents-core'; -import { getConfigFromEnv } from '@soat/embeddings-core'; -import { z } from '@ttoss/http-server-mcp'; - -const defaultStorageConfig: StorageConfig = { - type: 'local', - local: { - path: '/tmp/documents', - }, -}; - -const getEmbeddingConfig = (): EmbeddingConfig | undefined => { - try { - return getConfigFromEnv(); - } catch { - return undefined; - } -}; - -export const listDocumentsTool = { - name: 'list-documents', - description: 'List all documents', - inputSchema: z.object({}), - handler: async () => { - try { - const documents = await listDocuments(); - return { - content: [ - { - type: 'text', - text: JSON.stringify( - documents.map((doc) => { - return { - id: doc.id, - title: doc.title, - fileId: doc.fileId, - embeddingModel: doc.embeddingModel, - embeddingProvider: doc.embeddingProvider, - metadata: doc.metadata, - createdAt: doc.createdAt, - updatedAt: doc.updatedAt, - }; - }), - null, - 2 - ), - }, - ], - }; - } catch (error) { - return { - content: [ - { - type: 'text', - text: `Error listing documents: ${ - error instanceof Error ? error.message : 'Unknown error' - }`, - }, - ], - }; +import type { McpServer } from '@ttoss/http-server-mcp'; +import { apiCall, z } from '@ttoss/http-server-mcp'; + +const registerTools = (server: McpServer) => { + server.registerTool( + 'list-documents', + { + description: + 'List documents. If projectId is omitted, returns all documents accessible to the caller.', + inputSchema: { + projectId: z.string().optional().describe('Project ID (optional)'), + limit: z + .number() + .optional() + .describe('Maximum number of results to return (default 50)'), + offset: z + .number() + .optional() + .describe('Number of results to skip (default 0)'), + }, + }, + async ({ projectId, limit, offset }) => { + const params = new URLSearchParams(); + if (projectId) params.set('projectId', projectId); + if (limit !== undefined) params.set('limit', String(limit)); + if (offset !== undefined) params.set('offset', String(offset)); + const qs = params.toString() ? `?${params.toString()}` : ''; + const data = await apiCall('GET', `/documents${qs}`); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; } - }, -}; - -export const createDocumentTool = { - name: 'create-document', - description: 'Create a new document', - inputSchema: z.object({ - content: z.string().describe('The content of the document'), - title: z - .string() - .optional() - .describe('The title of the document (optional)'), - metadata: z - .record(z.any()) - .optional() - .describe('Additional metadata for the document (optional)'), - generateEmbedding: z - .boolean() - .optional() - .describe('Whether to generate embeddings for the document (optional)'), - }), - handler: async (args: { - content: string; - title?: string; - metadata?: Record; - generateEmbedding?: boolean; - }) => { - try { - const embeddingConfig = getEmbeddingConfig(); - - const document = await createDocument({ - storageConfig: defaultStorageConfig, - embeddingConfig, - content: args.content, - options: { - title: args.title, - metadata: args.metadata, - generateEmbedding: args.generateEmbedding, - }, - }); - - return { - content: [ - { - type: 'text', - text: JSON.stringify( - { - id: document.id, - title: document.title, - fileId: document.fileId, - embeddingModel: document.embeddingModel, - embeddingProvider: document.embeddingProvider, - hasEmbedding: !!document.embedding, - metadata: document.metadata, - createdAt: document.createdAt, - updatedAt: document.updatedAt, - }, - null, - 2 - ), - }, - ], - }; - } catch (error) { - return { - content: [ - { - type: 'text', - text: `Error creating document: ${ - error instanceof Error ? error.message : 'Unknown error' - }`, - }, - ], - }; - } - }, -}; - -export const getDocumentTool = { - name: 'get-document', - description: 'Get a document by ID', - inputSchema: z.object({ - id: z.string().describe('The ID of the document to retrieve'), - }), - handler: async (args: { id: string }) => { - try { - const document = await getDocument({ - storageConfig: defaultStorageConfig, - id: args.id, - }); - - if (!document) { + ); + + server.registerTool( + 'get-document', + { + description: 'Get a document by ID including its text content', + inputSchema: { + id: z.string().describe('Document ID'), + }, + }, + async ({ id }) => { + try { + const data = await apiCall('GET', `/documents/${id}`); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } catch (error) { return { content: [ { type: 'text', - text: 'Document not found', + text: JSON.stringify({ + error: 'not_found', + message: String(error), + }), }, ], }; } - - return { - content: [ - { - type: 'text', - text: JSON.stringify( - { - id: document.id, - title: document.title, - fileId: document.fileId, - content: document.content?.toString(), - embeddingModel: document.embeddingModel, - embeddingProvider: document.embeddingProvider, - hasEmbedding: !!document.embedding, - metadata: document.metadata, - createdAt: document.createdAt, - updatedAt: document.updatedAt, - }, - null, - 2 - ), - }, - ], - }; - } catch (error) { - return { - content: [ - { - type: 'text', - text: `Error getting document: ${ - error instanceof Error ? error.message : 'Unknown error' - }`, - }, - ], - }; } - }, -}; - -export const updateDocumentTool = { - name: 'update-document', - description: 'Update an existing document', - inputSchema: z.object({ - id: z.string().describe('The ID of the document to update'), - content: z - .string() - .optional() - .describe('The new content of the document (optional)'), - title: z - .string() - .optional() - .describe('The new title of the document (optional)'), - metadata: z - .record(z.any()) - .optional() - .describe('The new metadata for the document (optional)'), - regenerateEmbedding: z - .boolean() - .optional() - .describe('Whether to regenerate embeddings for the document (optional)'), - }), - handler: async (args: { - id: string; - content?: string; - title?: string; - metadata?: Record; - regenerateEmbedding?: boolean; - }) => { - try { - const embeddingConfig = getEmbeddingConfig(); - - const document = await updateDocument({ - storageConfig: defaultStorageConfig, - embeddingConfig, - id: args.id, - content: args.content, - title: args.title, - metadata: args.metadata, - regenerateEmbedding: args.regenerateEmbedding, + ); + + server.registerTool( + 'create-document', + { + description: + 'Create a new text document with an embedding vector for semantic search. project keys infer the project automatically; JWT callers must supply projectId.', + inputSchema: { + projectId: z + .string() + .optional() + .describe( + 'Project ID (required for JWT auth, optional for project keys)' + ), + content: z.string().describe('Text content of the document'), + filename: z.string().optional().describe('Optional filename'), + title: z.string().optional().describe('Optional document title'), + metadata: z + .record(z.unknown()) + .optional() + .describe('Arbitrary key-value metadata'), + tags: z.array(z.string()).optional().describe('Optional list of tags'), + }, + }, + async ({ projectId, content, filename, title, metadata, tags }) => { + const data = await apiCall('POST', '/documents', { + body: { projectId, content, filename, title, metadata, tags }, }); - - if (!document) { + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'delete-document', + { + description: 'Delete a document and its underlying file', + inputSchema: { + id: z.string().describe('Document ID'), + }, + }, + async ({ id }) => { + try { + await apiCall('DELETE', `/documents/${id}`); return { content: [ { type: 'text', - text: 'Document not found', + text: JSON.stringify({ id, deleted: true }), }, ], }; - } - - return { - content: [ - { - type: 'text', - text: JSON.stringify( - { - id: document.id, - title: document.title, - fileId: document.fileId, - content: document.content?.toString(), - embeddingModel: document.embeddingModel, - embeddingProvider: document.embeddingProvider, - hasEmbedding: !!document.embedding, - metadata: document.metadata, - createdAt: document.createdAt, - updatedAt: document.updatedAt, - }, - null, - 2 - ), - }, - ], - }; - } catch (error) { - return { - content: [ - { - type: 'text', - text: `Error updating document: ${ - error instanceof Error ? error.message : 'Unknown error' - }`, - }, - ], - }; - } - }, -}; - -export const deleteDocumentTool = { - name: 'delete-document', - description: 'Delete a document by ID', - inputSchema: z.object({ - id: z.string().describe('The ID of the document to delete'), - }), - handler: async (args: { id: string }) => { - try { - const deleted = await deleteDocument({ - storageConfig: defaultStorageConfig, - id: args.id, - }); - - if (!deleted) { + } catch (error) { return { content: [ { type: 'text', - text: 'Document not found', + text: JSON.stringify({ + id, + deleted: false, + error: String(error), + }), }, ], }; } - - return { - content: [ - { - type: 'text', - text: 'Document deleted successfully', - }, - ], - }; - } catch (error) { - return { - content: [ - { - type: 'text', - text: `Error deleting document: ${ - error instanceof Error ? error.message : 'Unknown error' - }`, - }, - ], - }; } - }, -}; - -export const searchDocumentsTool = { - name: 'search-documents', - description: 'Search documents by similarity using embeddings', - inputSchema: z.object({ - query: z.string().describe('The search query'), - limit: z - .number() - .optional() - .describe('Maximum number of documents to return (optional)'), - threshold: z - .number() - .optional() - .describe('Similarity threshold (optional)'), - }), - handler: async (args: { - query: string; - limit?: number; - threshold?: number; - }) => { - try { - const embeddingConfig = getEmbeddingConfig(); - if (!embeddingConfig) { + ); + + server.registerTool( + 'search-documents', + { + description: + 'Perform semantic search over documents using cosine similarity. If projectId is omitted, searches across all accessible projects.', + inputSchema: { + projectId: z.string().optional().describe('Project ID (optional)'), + query: z.string().describe('Natural language search query'), + limit: z + .number() + .optional() + .describe('Maximum number of results (default: 10)'), + threshold: z + .number() + .optional() + .describe( + 'Minimum similarity score (0-1). Only results with score >= threshold are returned.' + ), + tags: z + .array(z.string()) + .optional() + .describe('Filter to documents with any of these tags'), + }, + }, + async ({ projectId, query, limit, threshold, tags }) => { + const data = await apiCall('POST', '/documents/search', { + body: { projectId, query, limit, threshold, tags }, + }); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'update-document', + { + description: + 'Update a document by ID. Can update content (re-embeds), title, metadata, or tags.', + inputSchema: { + id: z.string().describe('Document ID'), + content: z + .string() + .optional() + .describe('New text content (re-computes the embedding)'), + title: z.string().optional().describe('New title'), + metadata: z + .record(z.unknown()) + .optional() + .describe('New metadata object'), + tags: z.array(z.string()).optional().describe('New list of tags'), + }, + }, + async ({ id, content, title, metadata, tags }) => { + try { + const data = await apiCall('PATCH', `/documents/${id}`, { + body: { content, title, metadata, tags }, + }); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } catch (error) { return { content: [ { type: 'text', - text: 'Embedding configuration is required for search. Set EMBEDDINGS_OLLAMA_MODEL or EMBEDDINGS_OPENAI_KEY', + text: JSON.stringify({ + error: 'not_found', + message: String(error), + }), }, ], }; } - - const documents = await searchDocumentsBySimilarity({ - storageConfig: defaultStorageConfig, - embeddingConfig, - query: args.query, - options: { - limit: args.limit, - threshold: args.threshold, - }, - }); - - return { - content: [ - { - type: 'text', - text: JSON.stringify( - documents.map((doc) => { - return { - id: doc.id, - title: doc.title, - fileId: doc.fileId, - content: doc.content?.toString(), - embeddingModel: doc.embeddingModel, - embeddingProvider: doc.embeddingProvider, - metadata: doc.metadata, - createdAt: doc.createdAt, - updatedAt: doc.updatedAt, - }; - }), - null, - 2 - ), - }, - ], - }; - } catch (error) { - return { - content: [ - { - type: 'text', - text: `Error searching documents: ${ - error instanceof Error ? error.message : 'Unknown error' - }`, - }, - ], - }; } - }, + ); }; + +export { registerTools }; diff --git a/packages/server/src/mcp/tools/files.ts b/packages/server/src/mcp/tools/files.ts new file mode 100644 index 00000000..555b56f2 --- /dev/null +++ b/packages/server/src/mcp/tools/files.ts @@ -0,0 +1,183 @@ +import type { McpServer } from '@ttoss/http-server-mcp'; +import { apiCall, z } from '@ttoss/http-server-mcp'; + +const registerTools = (server: McpServer) => { + server.registerTool( + 'list-files', + { + description: 'List files. Optionally filter by projectId.', + inputSchema: { + projectId: z.string().optional().describe('Project ID to filter by'), + limit: z + .number() + .optional() + .describe('Maximum number of results to return (default 50)'), + offset: z + .number() + .optional() + .describe('Number of results to skip (default 0)'), + }, + }, + async ({ projectId, limit, offset }) => { + const params = new URLSearchParams(); + if (projectId) params.set('projectId', projectId); + if (limit !== undefined) params.set('limit', String(limit)); + if (offset !== undefined) params.set('offset', String(offset)); + const qs = params.toString() ? `?${params.toString()}` : ''; + const data = await apiCall('GET', `/files${qs}`); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'get-file', + { + description: 'Get a file by ID', + inputSchema: { + id: z.string().describe('File ID'), + }, + }, + async ({ id }) => { + const data = await apiCall('GET', `/files/${id}`); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'upload-file', + { + description: + 'Upload a file to the server. The file content must be provided as a base64-encoded string.', + inputSchema: { + projectId: z.string().describe('Project ID to associate the file with'), + content: z.string().describe('Base64-encoded file content'), + filename: z.string().optional().describe('Original filename'), + contentType: z + .string() + .optional() + .describe('MIME content type, e.g. text/plain'), + mimeType: z + .string() + .optional() + .describe('Alias for contentType (MIME type, e.g. text/plain)'), + metadata: z + .string() + .optional() + .describe('Additional metadata as a JSON string'), + }, + }, + async ({ + projectId, + content, + filename, + contentType, + mimeType, + metadata, + }) => { + const data = await apiCall('POST', '/files/upload/base64', { + body: { + projectId, + content, + filename, + contentType: contentType || mimeType, + metadata, + }, + }); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'download-file', + { + description: 'Download a file by ID and return its content as base64', + inputSchema: { + id: z.string().describe('File ID'), + }, + }, + async ({ id }) => { + const data = await apiCall('GET', `/files/${id}/download/base64`); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'update-file-metadata', + { + description: 'Update the metadata and/or filename of a file', + inputSchema: { + id: z.string().describe('File ID'), + metadata: z + .string() + .optional() + .describe('New metadata as a JSON string'), + filename: z.string().optional().describe('New filename'), + }, + }, + async ({ id, metadata, filename }) => { + const data = await apiCall('PATCH', `/files/${id}/metadata`, { + body: { metadata, filename }, + }); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'create-file', + { + description: 'Create a file metadata record without uploading content', + inputSchema: { + storageType: z + .enum(['local', 's3', 'gcs']) + .describe('Storage backend type'), + storagePath: z.string().describe('Path in the storage backend'), + filename: z.string().optional().describe('Original filename'), + contentType: z.string().optional().describe('MIME content type'), + size: z.number().optional().describe('File size in bytes'), + metadata: z.string().optional().describe('Additional metadata'), + }, + }, + async (body) => { + const data = await apiCall('POST', '/files', { body }); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'delete-file', + { + description: 'Delete a file by ID', + inputSchema: { + id: z.string().describe('File ID'), + }, + }, + async ({ id }) => { + try { + await apiCall('DELETE', `/files/${id}`); + return { + content: [ + { + type: 'text', + text: JSON.stringify({ id, deleted: true }), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: 'text', + text: JSON.stringify({ + id, + deleted: false, + error: String(error), + }), + }, + ], + }; + } + } + ); +}; + +export { registerTools }; diff --git a/packages/server/src/mcp/tools/index.ts b/packages/server/src/mcp/tools/index.ts index ccf1cab7..c1620f3d 100644 --- a/packages/server/src/mcp/tools/index.ts +++ b/packages/server/src/mcp/tools/index.ts @@ -1,9 +1,21 @@ -// Export all MCP tools here -export { - createDocumentTool, - deleteDocumentTool, - getDocumentTool, - listDocumentsTool, - searchDocumentsTool, - updateDocumentTool, -} from './documents'; +import type { McpServer } from '@ttoss/http-server-mcp'; + +import { registerTools as registerActorTools } from './actors'; +import { registerTools as registerAiProviderTools } from './aiProviders'; +import { registerTools as registerConversationTools } from './conversations'; +import { registerTools as registerDocumentTools } from './documents'; +import { registerTools as registerFileTools } from './files'; +import { registerTools as registerProjectTools } from './projects'; +import { registerTools as registerSecretTools } from './secrets'; + +const registerTools = (server: McpServer) => { + registerActorTools(server); + registerAiProviderTools(server); + registerConversationTools(server); + registerDocumentTools(server); + registerFileTools(server); + registerProjectTools(server); + registerSecretTools(server); +}; + +export { registerTools }; diff --git a/packages/server/src/mcp/tools/projects.ts b/packages/server/src/mcp/tools/projects.ts new file mode 100644 index 00000000..43ec177e --- /dev/null +++ b/packages/server/src/mcp/tools/projects.ts @@ -0,0 +1,32 @@ +import type { McpServer } from '@ttoss/http-server-mcp'; +import { apiCall, z } from '@ttoss/http-server-mcp'; + +const registerTools = (server: McpServer) => { + server.registerTool( + 'list-projects', + { + description: 'List all projects accessible to the current user', + inputSchema: {}, + }, + async () => { + const data = await apiCall('GET', '/projects'); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'get-project', + { + description: 'Get a project by ID', + inputSchema: { + id: z.string().describe('Project ID'), + }, + }, + async ({ id }) => { + const data = await apiCall('GET', `/projects/${id}`); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); +}; + +export { registerTools }; diff --git a/packages/server/src/mcp/tools/secrets.ts b/packages/server/src/mcp/tools/secrets.ts new file mode 100644 index 00000000..9f9c0bfe --- /dev/null +++ b/packages/server/src/mcp/tools/secrets.ts @@ -0,0 +1,107 @@ +import type { McpServer } from '@ttoss/http-server-mcp'; +import { apiCall, z } from '@ttoss/http-server-mcp'; + +const registerTools = (server: McpServer) => { + server.registerTool( + 'list-secrets', + { + description: 'List secrets in a project. Values are never returned.', + inputSchema: { + projectId: z.string().optional().describe('Project ID to filter by'), + }, + }, + async ({ projectId }) => { + const params = new URLSearchParams(); + if (projectId) params.set('projectId', projectId); + const qs = params.toString() ? `?${params.toString()}` : ''; + const data = await apiCall('GET', `/secrets${qs}`); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'get-secret', + { + description: 'Get a secret by ID. The value is never returned.', + inputSchema: { + id: z.string().describe('Secret ID'), + }, + }, + async ({ id }) => { + const data = await apiCall('GET', `/secrets/${id}`); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'create-secret', + { + description: + 'Create a new secret. The value is encrypted at rest and never returned.', + inputSchema: { + projectId: z + .string() + .optional() + .describe( + 'Project ID. Required for JWT auth; omit when using a project key.' + ), + name: z.string().describe('Secret name'), + value: z + .string() + .optional() + .describe('Secret value to encrypt and store'), + }, + }, + async ({ projectId, name, value }) => { + const data = await apiCall('POST', '/secrets', { + body: { projectId, name, value }, + }); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'update-secret', + { + description: 'Update the name or value of a secret.', + inputSchema: { + id: z.string().describe('Secret ID'), + name: z.string().optional().describe('New name'), + value: z + .string() + .optional() + .describe('New secret value to encrypt and store'), + }, + }, + async ({ id, name, value }) => { + const data = await apiCall('PATCH', `/secrets/${id}`, { + body: { name, value }, + }); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); + + server.registerTool( + 'delete-secret', + { + description: + 'Delete a secret. Returns a conflict error if referenced by an AI provider unless force is true.', + inputSchema: { + id: z.string().describe('Secret ID'), + force: z + .boolean() + .optional() + .describe('If true, also delete dependent AI providers'), + }, + }, + async ({ id, force }) => { + const params = new URLSearchParams(); + if (force) params.set('force', 'true'); + const qs = params.toString() ? `?${params.toString()}` : ''; + const data = await apiCall('DELETE', `/secrets/${id}${qs}`); + return { content: [{ type: 'text', text: JSON.stringify(data) }] }; + } + ); +}; + +export { registerTools }; diff --git a/packages/server/src/middleware/auth.ts b/packages/server/src/middleware/auth.ts new file mode 100644 index 00000000..747d77c5 --- /dev/null +++ b/packages/server/src/middleware/auth.ts @@ -0,0 +1,164 @@ +import { PROJECT_KEY_RAW_PREFIX } from '@soat/postgresdb'; +import bcrypt from 'bcryptjs'; +import jwt from 'jsonwebtoken'; + +import type { Context } from '../Context'; +import { + createProjectKeyIsAllowed, + createJwtIsAllowed, +} from '../lib/permissions'; + +export const JWT_SECRET = process.env.JWT_SECRET ?? 'dev-secret'; + +export const BCRYPT_SALT_ROUNDS = 12; + +export const hashPassword = (password: string) => { + return bcrypt.hash(password, BCRYPT_SALT_ROUNDS); +}; + +export const comparePassword = (password: string, hash: string) => { + return bcrypt.compare(password, hash); +}; + +export const signUserToken = (payload: { publicId: string; role: string }) => { + return jwt.sign(payload, JWT_SECRET, { expiresIn: '7d' }); +}; + +type Next = () => Promise; + +const resolveProjectKey = async (ctx: Context, rawKey: string) => { + const keyPrefix = rawKey.substring(0, 8); + + const candidates = await ctx.db.ProjectKey.findAll({ + where: { keyPrefix }, + include: [{ model: ctx.db.Project }, { model: ctx.db.User }], + }); + + for (const row of candidates) { + const match = await bcrypt.compare(rawKey, row.keyHash as string); + if (match) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const projectPublicId = (row as any).project?.publicId as string; + const projectKeyPolicyId = row.policyId as number; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const keyUser = (row as any).user; + // Get user's project memberships + const project = await ctx.db.Project.findOne({ + where: { publicId: projectPublicId }, + }); + const membership = await ctx.db.UserProject.findOne({ + where: { userId: keyUser.id, projectId: project?.id }, + }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const userPolicyIds = ((membership as any)?.policyIds as number[]) ?? []; + + const projectKeyIsAllowed = createProjectKeyIsAllowed({ + projectPublicId, + userPolicyIds, + projectKeyPolicyId, + db: ctx.db, + }); + + ctx.authUser = { + id: keyUser.id as number, + publicId: keyUser.publicId as string, + username: keyUser.username as string, + role: keyUser.role as 'admin' | 'user', + projectKeyProjectId: projectPublicId, + isAllowed: projectKeyIsAllowed, + resolveProjectIds: async ({ projectPublicId: reqId, action }) => { + const targetId = reqId ?? projectPublicId; + const allowed = await projectKeyIsAllowed({ + projectPublicId: targetId, + action, + }); + if (!allowed) return null; + const proj = await ctx.db.Project.findOne({ + where: { publicId: targetId }, + }); + if (!proj) return null; + return [proj.id as number]; + }, + }; + break; + } + } +}; + +const resolveJwt = async (ctx: Context, token: string) => { + let payload: { publicId: string; role: string }; + + try { + payload = jwt.verify(token, JWT_SECRET) as typeof payload; + } catch { + return; + } + + const user = await ctx.db.User.findOne({ + where: { publicId: payload.publicId }, + }); + + if (!user) { + return; + } + + const userId = user.id as number; + const role = user.role as 'admin' | 'user'; + + const jwtIsAllowed = createJwtIsAllowed({ role, userId, db: ctx.db }); + + ctx.authUser = { + id: userId, + publicId: user.publicId as string, + username: user.username as string, + role, + isAllowed: jwtIsAllowed, + resolveProjectIds: async ({ projectPublicId, action }) => { + if (projectPublicId) { + const allowed = await jwtIsAllowed({ projectPublicId, action }); + if (!allowed) return null; + const proj = await ctx.db.Project.findOne({ + where: { publicId: projectPublicId }, + }); + if (!proj) return null; + return [proj.id as number]; + } + if (role === 'admin') return undefined; + const memberships = await ctx.db.UserProject.findAll({ + where: { userId }, + include: [{ model: ctx.db.Project }], + }); + const accessible: number[] = []; + for (const membership of memberships) { + const proj = ( + membership as unknown as { + project: InstanceType<(typeof ctx.db)['Project']>; + } + ).project; + if (!proj) continue; + const allowed = await jwtIsAllowed({ + projectPublicId: proj.publicId as string, + action, + }); + if (allowed) accessible.push(proj.id as number); + } + return accessible; + }, + }; +}; + +export const authMiddleware = async (ctx: Context, next: Next) => { + const authHeader: string | undefined = ctx.headers?.authorization; + + if (authHeader?.startsWith('Bearer ')) { + const token = authHeader.slice(7); + + if (token.startsWith(PROJECT_KEY_RAW_PREFIX)) { + await resolveProjectKey(ctx, token); + } else { + await resolveJwt(ctx, token); + } + } + + await next(); +}; diff --git a/packages/server/src/rest/openapi/v1/actors.yaml b/packages/server/src/rest/openapi/v1/actors.yaml new file mode 100644 index 00000000..63616c5c --- /dev/null +++ b/packages/server/src/rest/openapi/v1/actors.yaml @@ -0,0 +1,223 @@ +openapi: 3.0.3 +info: + title: SOAT Actors API + version: 1.0.0 + description: API for managing actors (e.g. WhatsApp contacts) associated with projects + contact: + name: SOAT Team + url: https://github.com/ttoss/soat +servers: + - url: http://0.0.0.0:5047/api/v1 + description: Development server +paths: + /actors: + get: + tags: + - Actors + summary: List actors + description: Returns all actors the caller has access to. If projectId is provided, returns only actors in that project. project keys are scoped to a single project automatically. JWT users without projectId receive actors across all their accessible projects. + operationId: listActors + parameters: + - name: projectId + in: query + required: false + description: Project ID (optional) + schema: + type: string + example: 'proj_V1StGXR8Z5jdHi6B' + - name: externalId + in: query + required: false + description: External ID to filter by (e.g. WhatsApp phone number) + schema: + type: string + example: '+15551234567' + responses: + '200': + description: List of actors + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ActorRecord' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + post: + tags: + - Actors + summary: Create an actor + description: Creates a new actor. project keys automatically infer the project from the key's scope; JWT callers must supply projectId. + operationId: createActor + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - name + properties: + projectId: + type: string + description: Project ID. Required for JWT auth; omit when using an project key. + example: 'proj_V1StGXR8Z5jdHi6B' + name: + type: string + example: 'Alice' + type: + type: string + description: Optional actor type (e.g. 'customer', 'agent') + example: 'customer' + externalId: + type: string + description: Optional external identifier (e.g. WhatsApp phone number). Must be unique within a project. + example: '+15551234567' + responses: + '201': + description: Actor created + content: + application/json: + schema: + $ref: '#/components/schemas/ActorRecord' + '400': + description: Invalid request body + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /actors/{id}: + get: + tags: + - Actors + summary: Get an actor by ID + description: Returns an actor by its ID + operationId: getActor + parameters: + - name: id + in: path + required: true + description: Actor ID + schema: + type: string + example: 'act_V1StGXR8Z5jdHi6B' + responses: + '200': + description: Actor found + content: + application/json: + schema: + $ref: '#/components/schemas/ActorRecord' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Actor not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + delete: + tags: + - Actors + summary: Delete an actor + description: Deletes an actor by its ID + operationId: deleteActor + parameters: + - name: id + in: path + required: true + description: Actor ID + schema: + type: string + example: 'act_V1StGXR8Z5jdHi6B' + responses: + '204': + description: Actor deleted + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Actor not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' +components: + schemas: + ActorRecord: + type: object + properties: + id: + type: string + description: Actor ID + example: 'act_V1StGXR8Z5jdHi6B' + projectId: + type: string + description: Project ID + example: 'proj_V1StGXR8Z5jdHi6B' + name: + type: string + example: 'Alice' + type: + type: string + nullable: true + description: Actor type (e.g. 'customer', 'agent') + example: 'customer' + externalId: + type: string + nullable: true + description: External identifier (e.g. WhatsApp phone number) + example: '+15551234567' + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + ErrorResponse: + type: object + properties: + error: + type: string + example: 'Actor not found' diff --git a/packages/server/src/rest/openapi/v1/conversations.yaml b/packages/server/src/rest/openapi/v1/conversations.yaml new file mode 100644 index 00000000..17c726a9 --- /dev/null +++ b/packages/server/src/rest/openapi/v1/conversations.yaml @@ -0,0 +1,508 @@ +openapi: 3.0.3 +info: + title: SOAT Conversations API + version: 1.0.0 + description: API for managing conversations and their messages + contact: + name: SOAT Team + url: https://github.com/ttoss/soat +servers: + - url: http://0.0.0.0:5047/api/v1 + description: Development server +paths: + /conversations: + get: + tags: + - Conversations + summary: List conversations + description: Returns all conversations the caller has access to. If projectId is provided, returns only conversations in that project. project keys are scoped to a single project automatically. + operationId: listConversations + parameters: + - name: projectId + in: query + required: false + description: Project ID (optional) + schema: + type: string + example: 'proj_V1StGXR8Z5jdHi6B' + - name: actorId + in: query + required: false + description: Filter by actor ID + schema: + type: string + example: 'act_V1StGXR8Z5jdHi6B' + responses: + '200': + description: List of conversations + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ConversationRecord' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + post: + tags: + - Conversations + summary: Create a conversation + description: Creates a new conversation. project keys automatically infer the project from the key's scope; JWT callers must supply projectId. + operationId: createConversation + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + projectId: + type: string + description: Project ID. Required for JWT auth; omit when using an project key. + example: 'proj_V1StGXR8Z5jdHi6B' + status: + type: string + enum: [open, closed] + default: open + description: Initial conversation status + responses: + '201': + description: Conversation created + content: + application/json: + schema: + $ref: '#/components/schemas/ConversationRecord' + '400': + description: Invalid request body + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /conversations/{id}: + get: + tags: + - Conversations + summary: Get a conversation by ID + description: Returns a conversation by its ID + operationId: getConversation + parameters: + - name: id + in: path + required: true + description: Conversation ID + schema: + type: string + example: 'conv_V1StGXR8Z5jdHi6B' + responses: + '200': + description: Conversation found + content: + application/json: + schema: + $ref: '#/components/schemas/ConversationRecord' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Conversation not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + patch: + tags: + - Conversations + summary: Update a conversation + description: Updates the status of a conversation + operationId: updateConversation + parameters: + - name: id + in: path + required: true + description: Conversation ID + schema: + type: string + example: 'conv_V1StGXR8Z5jdHi6B' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - status + properties: + status: + type: string + enum: [open, closed] + description: New conversation status + responses: + '200': + description: Conversation updated + content: + application/json: + schema: + $ref: '#/components/schemas/ConversationRecord' + '400': + description: Invalid request body + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Conversation not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + delete: + tags: + - Conversations + summary: Delete a conversation + description: Deletes a conversation by its ID + operationId: deleteConversation + parameters: + - name: id + in: path + required: true + description: Conversation ID + schema: + type: string + example: 'conv_V1StGXR8Z5jdHi6B' + responses: + '204': + description: Conversation deleted + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Conversation not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /conversations/{id}/messages: + get: + tags: + - Conversations + summary: List conversation messages + description: Returns all messages (documents) attached to a conversation, ordered by position + operationId: listConversationMessages + parameters: + - name: id + in: path + required: true + description: Conversation ID + schema: + type: string + example: 'conv_V1StGXR8Z5jdHi6B' + responses: + '200': + description: List of messages + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ConversationMessageRecord' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Conversation not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + post: + tags: + - Conversations + summary: Add a message to a conversation + description: Creates a document from the message text and attaches it to the conversation at the given position. If position is omitted, it is appended at the end. + operationId: addConversationMessage + parameters: + - name: id + in: path + required: true + description: Conversation ID + schema: + type: string + example: 'conv_V1StGXR8Z5jdHi6B' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - message + - actorId + properties: + message: + type: string + description: Message text content to add to the conversation + example: 'Hello, how can I help you?' + actorId: + type: string + description: Actor ID who is sending this message + example: 'act_V1StGXR8Z5jdHi6B' + position: + type: integer + description: Zero-based position. Defaults to MAX+1 (append). + example: 0 + responses: + '201': + description: Message added + content: + application/json: + schema: + $ref: '#/components/schemas/ConversationMessageRecord' + '400': + description: Invalid request body + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Conversation or actor not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /conversations/{id}/actors: + get: + tags: + - Conversations + summary: List actors in a conversation + description: Returns all distinct actors who have sent at least one message in the conversation + operationId: listConversationActors + parameters: + - name: id + in: path + required: true + description: Conversation ID + schema: + type: string + example: 'conv_V1StGXR8Z5jdHi6B' + responses: + '200': + description: List of actors + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ConversationActorRecord' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Conversation not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /conversations/{id}/messages/{documentId}: + delete: + tags: + - Conversations + summary: Remove a message from a conversation + description: Removes a document from a conversation + operationId: removeConversationMessage + parameters: + - name: id + in: path + required: true + description: Conversation ID + schema: + type: string + example: 'conv_V1StGXR8Z5jdHi6B' + - name: documentId + in: path + required: true + description: Document ID + schema: + type: string + example: 'doc_V1StGXR8Z5jdHi6B' + responses: + '204': + description: Message removed + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Conversation or message not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' +components: + schemas: + ConversationRecord: + type: object + properties: + id: + type: string + description: Conversation ID + example: 'conv_V1StGXR8Z5jdHi6B' + projectId: + type: string + description: Project ID + example: 'proj_V1StGXR8Z5jdHi6B' + status: + type: string + enum: [open, closed] + description: Conversation status + example: 'open' + createdAt: + type: string + format: date-time + description: Creation timestamp + updatedAt: + type: string + format: date-time + description: Last update timestamp + ConversationMessageRecord: + type: object + properties: + documentId: + type: string + description: Document ID + example: 'doc_V1StGXR8Z5jdHi6B' + actorId: + type: string + description: Actor ID who sent this message + example: 'act_V1StGXR8Z5jdHi6B' + position: + type: integer + description: Zero-based position in the conversation + example: 0 + ConversationActorRecord: + type: object + properties: + id: + type: string + description: Actor ID + example: 'act_V1StGXR8Z5jdHi6B' + projectId: + type: string + description: Project ID + example: 'proj_V1StGXR8Z5jdHi6B' + name: + type: string + description: Actor name + example: 'Alice' + type: + type: string + description: Actor type + example: 'human' + externalId: + type: string + description: External identifier + example: 'ext_123' + createdAt: + type: string + format: date-time + description: Creation timestamp + updatedAt: + type: string + format: date-time + description: Last update timestamp + ErrorResponse: + type: object + properties: + error: + type: string + description: Error message + example: 'Conversation not found' diff --git a/packages/server/src/rest/openapi/v1/documents.yaml b/packages/server/src/rest/openapi/v1/documents.yaml index c11b857b..32fa8c2b 100644 --- a/packages/server/src/rest/openapi/v1/documents.yaml +++ b/packages/server/src/rest/openapi/v1/documents.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: SOAT Documents API version: 1.0.0 - description: API for managing documents with embeddings support + description: API for managing AI-indexed text documents with semantic search contact: name: SOAT Team url: https://github.com/ttoss/soat @@ -14,26 +14,34 @@ paths: get: tags: - Documents - summary: List all documents - description: Returns a list of all documents stored in the system + summary: List documents + description: Returns all documents the caller has access to. If projectId is provided, returns only documents in that project. project keys are scoped to a single project automatically. JWT users without projectId receive documents across all their accessible projects. operationId: listDocuments + parameters: + - name: projectId + in: query + required: false + description: Project ID (optional) + schema: + type: string + example: 'proj_V1StGXR8Z5jdHi6B' responses: '200': - description: Documents list returned successfully + description: List of documents content: application/json: schema: - type: object - properties: - success: - type: boolean - example: true - documents: - type: array - items: - $ref: '#/components/schemas/DocumentRecord' - '500': - description: Internal server error + type: array + items: + $ref: '#/components/schemas/DocumentRecord' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden content: application/json: schema: @@ -41,8 +49,8 @@ paths: post: tags: - Documents - summary: Create a new document - description: Creates a new document with optional embedding generation. The document content is saved as a markdown file. + summary: Create a document + description: Creates a new text document and generates an embedding vector for semantic search. project keys automatically infer the project from the key's scope; JWT callers must supply projectId. operationId: createDocument requestBody: required: true @@ -53,107 +61,37 @@ paths: required: - content properties: + projectId: + type: string + description: Project ID. Required for JWT auth; omit when using an project key. + example: 'proj_V1StGXR8Z5jdHi6B' content: type: string - description: Document content in markdown format - example: '# My Document\n\nThis is the content of my document.' - title: + example: 'The quick brown fox jumps over the lazy dog.' + filename: type: string - description: Document title - example: 'My Document' - metadata: - type: object - additionalProperties: true - description: Additional metadata for the document - example: - author: 'John Doe' - tags: ['example', 'documentation'] - generateEmbedding: - type: boolean - description: Whether to generate embedding for the document (default true if embedding config is available) - default: true + example: 'my-doc.txt' responses: '201': - description: Document created successfully + description: Document created content: application/json: schema: - type: object - properties: - success: - type: boolean - example: true - document: - $ref: '#/components/schemas/DocumentResponse' + $ref: '#/components/schemas/DocumentRecord' '400': - description: Bad request + description: Invalid request body content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - /documents/search: - get: - tags: - - Documents - summary: Search documents by similarity - description: Searches for documents similar to the query using vector similarity. Requires embedding configuration (EMBEDDINGS_OLLAMA_MODEL or EMBEDDINGS_OPENAI_KEY). - operationId: searchDocuments - parameters: - - name: query - in: query - required: true - description: Search query text - schema: - type: string - example: 'machine learning concepts' - - name: limit - in: query - required: false - description: Maximum number of results to return - schema: - type: integer - minimum: 1 - maximum: 100 - default: 10 - example: 5 - - name: threshold - in: query - required: false - description: Minimum similarity threshold (0-1) - schema: - type: number - minimum: 0 - maximum: 1 - example: 0.7 - responses: - '200': - description: Search results returned successfully - content: - application/json: - schema: - type: object - properties: - success: - type: boolean - example: true - documents: - type: array - items: - $ref: '#/components/schemas/DocumentWithContent' - '400': - description: Bad request (query missing or embedding not configured) + '401': + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - '500': - description: Internal server error + '403': + description: Forbidden content: application/json: schema: @@ -163,7 +101,7 @@ paths: tags: - Documents summary: Get a document by ID - description: Returns a document with its content + description: Returns a document with its text content operationId: getDocument parameters: - name: id @@ -172,45 +110,38 @@ paths: description: Document ID schema: type: string - format: uuid - example: '550e8400-e29b-41d4-a716-446655440000' + example: 'doc_V1StGXR8Z5jdHi6B' responses: '200': - description: Document returned successfully + description: Document found content: application/json: schema: - type: object - properties: - success: - type: boolean - example: true - document: - $ref: '#/components/schemas/DocumentWithContent' - '400': - description: Bad request + $ref: '#/components/schemas/DocumentRecord' + '401': + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - '404': - description: Document not found + '403': + description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - '500': - description: Internal server error + '404': + description: Document not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - put: + delete: tags: - Documents - summary: Update a document - description: Updates an existing document. If content is changed, a new file is created and the old one is deleted. Embedding can be regenerated. - operationId: updateDocument + summary: Delete a document + description: Deletes a document and its underlying file + operationId: deleteDocument parameters: - name: id in: path @@ -218,49 +149,18 @@ paths: description: Document ID schema: type: string - format: uuid - example: '550e8400-e29b-41d4-a716-446655440000' - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - content: - type: string - description: New document content in markdown format - example: '# Updated Document\n\nThis is the updated content.' - title: - type: string - description: New document title - example: 'Updated Title' - metadata: - type: object - additionalProperties: true - description: New metadata for the document - example: - author: 'Jane Doe' - version: 2 - regenerateEmbedding: - type: boolean - description: Whether to regenerate embedding when content changes (default true) - default: true + example: 'doc_V1StGXR8Z5jdHi6B' responses: - '200': - description: Document updated successfully + '204': + description: Document deleted + '401': + description: Unauthorized content: application/json: schema: - type: object - properties: - success: - type: boolean - example: true - document: - $ref: '#/components/schemas/DocumentWithContent' - '400': - description: Bad request + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden content: application/json: schema: @@ -271,52 +171,55 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - delete: + /documents/search: + post: tags: - Documents - summary: Delete a document - description: Deletes a document and its associated file - operationId: deleteDocument - parameters: - - name: id - in: path - required: true - description: Document ID - schema: - type: string - format: uuid - example: '550e8400-e29b-41d4-a716-446655440000' + summary: Semantic search over documents + description: Embeds the query text and returns the most similar documents using cosine distance. If projectId is omitted, searches across all projects the caller has access to. + operationId: searchDocuments + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - query + properties: + projectId: + type: string + description: Project ID (optional). Omit to search across all accessible projects. + example: 'proj_V1StGXR8Z5jdHi6B' + query: + type: string + example: 'What is the capital of France?' + limit: + type: integer + example: 5 responses: '200': - description: Document deleted successfully + description: Search results content: application/json: schema: - type: object - properties: - success: - type: boolean - example: true + type: array + items: + $ref: '#/components/schemas/DocumentRecord' '400': - description: Bad request + description: Invalid request body content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - '404': - description: Document not found + '401': + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - '500': - description: Internal server error + '403': + description: Forbidden content: application/json: schema: @@ -328,146 +231,38 @@ components: properties: id: type: string - format: uuid description: Document ID - example: '550e8400-e29b-41d4-a716-446655440000' - title: - type: string - description: Document title - example: 'My Document' + example: 'doc_V1StGXR8Z5jdHi6B' fileId: type: string - format: uuid - description: Associated file ID - example: '660e8400-e29b-41d4-a716-446655440001' - embeddingModel: + description: Underlying file ID + example: 'file_V1StGXR8Z5jdHi6B' + projectId: type: string - description: Model used to generate embedding - example: 'nomic-embed-text' - embeddingProvider: + description: Project ID + example: 'proj_V1StGXR8Z5jdHi6B' + filename: type: string - description: Provider used for embedding - enum: ['ollama', 'openai'] - example: 'ollama' - hasEmbedding: - type: boolean - description: Whether the document has an embedding - example: true - metadata: - type: object - additionalProperties: true - description: Document metadata - example: - author: 'John Doe' - createdAt: - type: string - format: date-time - description: Creation timestamp - example: '2026-01-07T10:00:00Z' - updatedAt: - type: string - format: date-time - description: Last update timestamp - example: '2026-01-07T10:00:00Z' - DocumentResponse: - type: object - properties: - id: - type: string - format: uuid - description: Document ID - example: '550e8400-e29b-41d4-a716-446655440000' - title: - type: string - description: Document title - example: 'My Document' - fileId: - type: string - format: uuid - description: Associated file ID - example: '660e8400-e29b-41d4-a716-446655440001' - embeddingModel: - type: string - description: Model used to generate embedding - example: 'nomic-embed-text' - embeddingProvider: - type: string - description: Provider used for embedding - enum: ['ollama', 'openai'] - example: 'ollama' - hasEmbedding: - type: boolean - description: Whether the document has an embedding - example: true - metadata: - type: object - additionalProperties: true - description: Document metadata - example: - author: 'John Doe' - createdAt: - type: string - format: date-time - description: Creation timestamp - example: '2026-01-07T10:00:00Z' - updatedAt: - type: string - format: date-time - description: Last update timestamp - example: '2026-01-07T10:00:00Z' - DocumentWithContent: - type: object - properties: - id: - type: string - format: uuid - description: Document ID - example: '550e8400-e29b-41d4-a716-446655440000' - title: - type: string - description: Document title - example: 'My Document' - fileId: - type: string - format: uuid - description: Associated file ID - example: '660e8400-e29b-41d4-a716-446655440001' + description: Original filename + example: 'my-doc.txt' + size: + type: integer + description: File size in bytes + example: 42 content: type: string - description: Document content - example: '# My Document\n\nThis is the content of my document.' - embeddingModel: - type: string - description: Model used to generate embedding - example: 'nomic-embed-text' - embeddingProvider: - type: string - description: Provider used for embedding - enum: ['ollama', 'openai'] - example: 'ollama' - metadata: - type: object - additionalProperties: true - description: Document metadata - example: - author: 'John Doe' + nullable: true + description: Text content (only present on getDocument) + example: 'The quick brown fox jumps over the lazy dog.' createdAt: type: string format: date-time - description: Creation timestamp - example: '2026-01-07T10:00:00Z' updatedAt: type: string format: date-time - description: Last update timestamp - example: '2026-01-07T10:00:00Z' ErrorResponse: type: object properties: - success: - type: boolean - example: false error: type: string - description: Error message - example: 'An error occurred' + example: 'Document not found' diff --git a/packages/server/src/rest/openapi/v1/files.yaml b/packages/server/src/rest/openapi/v1/files.yaml index e0ef67e1..4e700b8b 100644 --- a/packages/server/src/rest/openapi/v1/files.yaml +++ b/packages/server/src/rest/openapi/v1/files.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: SOAT Files API version: 1.0.0 - description: API para gerenciar arquivos (Files resource) + description: API for managing files (Files resource) contact: name: SOAT Team url: https://github.com/ttoss/soat @@ -14,37 +14,30 @@ paths: get: tags: - Files - summary: Lista todos os arquivos - description: Retorna uma lista com todos os arquivos armazenados + summary: List all files + description: Returns a list of all stored files operationId: listFiles responses: '200': - description: Lista de arquivos retornada com sucesso + description: List of files returned successfully content: application/json: schema: - type: object - properties: - success: - type: boolean - example: true - files: - type: array - items: - $ref: '#/components/schemas/FileRecord' + type: array + items: + $ref: '#/components/schemas/FileRecord' '500': - description: Erro interno do servidor + description: Internal server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - /files/upload: post: tags: - Files - summary: Faz upload de um arquivo - description: Cria um novo arquivo no sistema - operationId: uploadFile + summary: Create a file + description: Creates a new file record in the system + operationId: createFile requestBody: required: true content: @@ -52,97 +45,123 @@ paths: schema: type: object required: - - content + - storageType + - storagePath properties: - content: + filename: + type: string + description: Name of the file + example: 'document.pdf' + contentType: + type: string + description: MIME type of the file + example: 'application/pdf' + size: + type: integer + description: File size in bytes + example: 1024 + storageType: + type: string + enum: [local, s3, gcs] + description: Storage backend type + example: 'local' + storagePath: + type: string + description: Path where the file is stored + example: '/uploads/document.pdf' + metadata: type: string - description: Conteúdo do arquivo - example: 'Hello World!' - options: - type: object - properties: - contentType: - type: string - example: text/plain - metadata: - type: object - additionalProperties: true - example: - filename: test.txt + description: JSON string with additional metadata + example: '{"author":"John"}' responses: - '200': - description: Arquivo criado com sucesso + '201': + description: File created successfully content: application/json: schema: - type: object - properties: - success: - type: boolean - example: true - file: - type: object - properties: - id: - type: string - example: 'abc123' - '400': - description: Requisição inválida + $ref: '#/components/schemas/FileRecord' + '500': + description: Internal server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - '500': - description: Erro interno do servidor + /files/upload: + post: + tags: + - Files + summary: Upload a file + description: Uploads a file to the server and stores it in the configured storage directory + operationId: uploadFile + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + required: + - file + - projectId + properties: + file: + type: string + format: binary + description: File content + projectId: + type: string + description: Project ID to associate the file with + example: 'proj_V1StGXR8Z5jdHi6B' + metadata: + type: string + description: Additional metadata as a JSON string + example: '{"author":"John"}' + responses: + '201': + description: File uploaded successfully + content: + application/json: + schema: + $ref: '#/components/schemas/FileRecord' + '400': + description: Missing file or invalid project content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' /files/{id}: get: tags: - Files - summary: Obtém um arquivo por ID - description: Retorna os dados e metadados de um arquivo específico - operationId: getFileById + summary: Get a file by ID + description: Returns the data and metadata of a specific file + operationId: getFile parameters: - name: id in: path required: true - description: ID do arquivo + description: File ID schema: type: string example: 'abc123' responses: '200': - description: Arquivo encontrado - content: - application/json: - schema: - type: object - properties: - success: - type: boolean - example: true - file: - type: object - description: Conteúdo do arquivo - record: - $ref: '#/components/schemas/FileRecord' - '400': - description: Requisição inválida + description: File found content: application/json: schema: - $ref: '#/components/schemas/ErrorResponse' + $ref: '#/components/schemas/FileRecord' '404': - description: Arquivo não encontrado + description: File not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': - description: Erro interno do servidor + description: Internal server error content: application/json: schema: @@ -150,42 +169,108 @@ paths: delete: tags: - Files - summary: Deleta um arquivo - description: Remove um arquivo do sistema pelo ID + summary: Delete a file + description: Removes a file from the system by ID operationId: deleteFile parameters: - name: id in: path required: true - description: ID do arquivo a ser deletado + description: ID of the file to delete schema: type: string example: 'abc123' responses: - '200': - description: Arquivo deletado com sucesso + '204': + description: File deleted successfully + '404': + description: File not found content: application/json: schema: - type: object - properties: - success: - type: boolean - example: true - '400': - description: Requisição inválida + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' + /files/{id}/download: + get: + tags: + - Files + summary: Download a file + description: Streams the file content to the client + operationId: downloadFile + parameters: + - name: id + in: path + required: true + description: File ID + schema: + type: string + example: 'fil_abc123' + responses: + '200': + description: File content + content: + application/octet-stream: + schema: + type: string + format: binary + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' '404': - description: Arquivo não encontrado + description: File not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - '500': - description: Erro interno do servidor + /files/{id}/metadata: + patch: + tags: + - Files + summary: Update file metadata + description: Updates the metadata field of a file + operationId: updateFileMetadata + parameters: + - name: id + in: path + required: true + description: File ID + schema: + type: string + example: 'fil_abc123' + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + metadata: + type: string + description: New metadata as a JSON string + example: '{"author":"Jane","tags":["report"]}' + filename: + type: string + description: New filename for the file + example: 'renamed-file.txt' + responses: + '200': + description: Metadata updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/FileRecord' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + description: File not found content: application/json: schema: @@ -194,34 +279,62 @@ components: schemas: FileRecord: type: object - description: Metadados de um arquivo armazenado + description: Stored file metadata properties: id: type: string - description: Identificador único do arquivo - name: + description: Unique file identifier + example: 'abc123' + filename: type: string - description: Nome do arquivo + description: Name of the file + example: 'document.pdf' + contentType: + type: string + description: MIME type of the file + example: 'application/pdf' size: type: integer - description: Tamanho em bytes - contentType: + description: File size in bytes + example: 1024 + storageType: + type: string + enum: [local, s3, gcs] + description: Storage backend type + example: 'local' + storagePath: + type: string + description: Path where the file is stored + example: '/uploads/document.pdf' + metadata: type: string - description: Tipo MIME do arquivo + description: JSON string with additional metadata + example: '{"author":"John"}' createdAt: type: string format: date-time - description: Data de criação - metadata: - type: object - additionalProperties: true - description: Metadados adicionais + description: Creation timestamp + updatedAt: + type: string + format: date-time + description: Last update timestamp ErrorResponse: type: object properties: - success: - type: boolean - example: false error: type: string - description: Mensagem de erro + description: Error message + responses: + Unauthorized: + description: Authentication required + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + Forbidden: + description: Insufficient permissions + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + example: 'File not found' diff --git a/packages/server/src/rest/openapi/v1/users.yaml b/packages/server/src/rest/openapi/v1/users.yaml new file mode 100644 index 00000000..c7870cc8 --- /dev/null +++ b/packages/server/src/rest/openapi/v1/users.yaml @@ -0,0 +1,181 @@ +openapi: 3.0.3 +info: + title: SOAT Users API + version: 1.0.0 + description: API for managing users (Users resource) + contact: + name: SOAT Team + url: https://github.com/ttoss/soat +servers: + - url: http://0.0.0.0:5047/api/v1 + description: Development server +paths: + /users: + get: + tags: + - Users + summary: List all users + description: Returns a list of all users + operationId: listUsers + responses: + '200': + description: List of users returned successfully + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/UserRecord' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + post: + tags: + - Users + summary: Create a user + description: Creates a new user in the system + operationId: createUser + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - username + - password + properties: + username: + type: string + example: johndoe + password: + type: string + format: password + example: supersecret + role: + type: string + enum: [admin, user] + example: user + responses: + '201': + description: User created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/UserRecord' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /users/{id}: + get: + tags: + - Users + summary: Get a user by ID + description: Returns the data of a specific user + operationId: getUser + parameters: + - name: id + in: path + required: true + description: User ID + schema: + type: string + example: usr_V1StGXR8Z5jdHi6B + responses: + '200': + description: User found + content: + application/json: + schema: + $ref: '#/components/schemas/UserRecord' + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + /users/bootstrap: + post: + tags: + - Users + summary: Create the first admin user + description: Creates the first admin user. Returns 409 if any user already exists. + operationId: bootstrapUser + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - username + - password + properties: + username: + type: string + example: admin + password: + type: string + format: password + example: supersecret + responses: + '201': + description: Admin user created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/UserRecord' + '409': + description: Users already exist + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' +components: + schemas: + UserRecord: + type: object + properties: + id: + type: string + description: Public user ID (usr_ prefix) + example: usr_V1StGXR8Z5jdHi6B + username: + type: string + example: johndoe + role: + type: string + enum: [admin, user] + example: user + createdAt: + type: string + format: date-time + example: '2024-01-01T00:00:00.000Z' + updatedAt: + type: string + format: date-time + example: '2024-01-01T00:00:00.000Z' + ErrorResponse: + type: object + properties: + error: + type: string + example: 'An error occurred' diff --git a/packages/server/src/rest/v1/actors.ts b/packages/server/src/rest/v1/actors.ts new file mode 100644 index 00000000..c3faadd2 --- /dev/null +++ b/packages/server/src/rest/v1/actors.ts @@ -0,0 +1,781 @@ +import { Router } from '@ttoss/http-server'; +import type { Context } from 'src/Context'; +import { db } from 'src/db'; +import { + createActor, + deleteActor, + getActor, + getActorTags, + listActors, + updateActor, + updateActorTags, +} from 'src/lib/actors'; +import { buildSrn } from 'src/lib/iam'; + +const actorsRouter = new Router(); + +/** + * @openapi + * /actors: + * get: + * tags: + * - Actors + * summary: List actors + * description: Returns all actors the caller has access to. If projectId is provided, returns only actors in that project. API keys are scoped to a single project automatically. JWT users without projectId receive actors across all their accessible projects. + * operationId: listActors + * parameters: + * - name: projectId + * in: query + * required: false + * description: Project ID (optional) + * schema: + * type: string + * example: 'proj_V1StGXR8Z5jdHi6B' + * - name: externalId + * in: query + * required: false + * description: External ID to filter by (e.g. WhatsApp phone number) + * schema: + * type: string + * example: '+15551234567' + * - name: name + * in: query + * required: false + * description: Partial, case-insensitive name filter + * schema: + * type: string + * example: 'alice' + * - name: type + * in: query + * required: false + * description: Exact type filter (e.g. customer, agent) + * schema: + * type: string + * example: 'customer' + * - name: limit + * in: query + * required: false + * description: Maximum number of results to return (default 50) + * schema: + * type: integer + * example: 50 + * - name: offset + * in: query + * required: false + * description: Number of results to skip (default 0) + * schema: + * type: integer + * example: 0 + * responses: + * '200': + * description: List of actors + * content: + * application/json: + * schema: + * type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/ActorRecord' + * total: + * type: integer + * limit: + * type: integer + * offset: + * type: integer + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +actorsRouter.get('/actors', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const projectPublicId = ctx.query.projectId as string | undefined; + const externalId = ctx.query.externalId as string | undefined; + const name = ctx.query.name as string | undefined; + const type = ctx.query.type as string | undefined; + const limit = ctx.query.limit + ? parseInt(ctx.query.limit as string, 10) + : undefined; + const offset = ctx.query.offset + ? parseInt(ctx.query.offset as string, 10) + : undefined; + + const projectIds = await ctx.authUser.resolveProjectIds({ + projectPublicId, + action: 'actors:ListActors', + }); + + if (projectIds === null) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + ctx.body = await listActors({ + projectIds, + externalId, + name, + type, + limit, + offset, + }); +}); + +/** + * @openapi + * /actors/{id}: + * get: + * tags: + * - Actors + * summary: Get an actor by ID + * description: Returns an actor by its ID + * operationId: getActor + * parameters: + * - name: id + * in: path + * required: true + * description: Actor ID + * schema: + * type: string + * example: 'act_V1StGXR8Z5jdHi6B' + * responses: + * '200': + * description: Actor found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ActorRecord' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Actor not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +actorsRouter.get('/actors/:id', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const actor = await getActor({ id: ctx.params.id }); + + if (!actor) { + ctx.status = 404; + ctx.body = { error: 'Actor not found' }; + return; + } + + const srnGet = buildSrn({ + projectPublicId: actor.projectId!, + resourceType: 'actor', + resourceId: actor.id, + }); + const contextGet: Record = { 'soat:ResourceType': 'actor' }; + if (actor.tags) { + for (const [k, v] of Object.entries(actor.tags)) { + contextGet[`soat:ResourceTag/${k}`] = v as string; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: actor.projectId!, + action: 'actors:GetActor', + resource: srnGet, + context: contextGet, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + ctx.body = actor; +}); + +/** + * @openapi + * /actors: + * post: + * tags: + * - Actors + * summary: Create an actor + * description: Creates a new actor. API keys automatically infer the project from the key's scope; JWT callers must supply projectId. + * operationId: createActor + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - name + * properties: + * projectId: + * type: string + * description: Project ID. Required for JWT auth; omit when using an API key. + * example: 'proj_V1StGXR8Z5jdHi6B' + * name: + * type: string + * example: 'Alice' + * type: + * type: string + * description: Optional actor type (e.g. 'customer', 'agent') + * example: 'customer' + * externalId: + * type: string + * description: Optional external identifier (e.g. WhatsApp phone number). Must be unique within a project. + * example: '+15551234567' + * responses: + * '201': + * description: Actor created + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ActorRecord' + * '400': + * description: Invalid request body + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +actorsRouter.post('/actors', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const body = ctx.request.body as { + projectId?: string; + name: string; + type?: string; + externalId?: string; + }; + + if (!body.name) { + ctx.status = 400; + ctx.body = { error: 'name is required' }; + return; + } + + let resolvedProjectPublicId = body.projectId; + if (!resolvedProjectPublicId) { + if (ctx.authUser.projectKeyProjectId) { + resolvedProjectPublicId = ctx.authUser.projectKeyProjectId; + } else { + ctx.status = 400; + ctx.body = { error: 'projectId is required' }; + return; + } + } + + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: resolvedProjectPublicId, + action: 'actors:CreateActor', + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const project = await db.Project.findOne({ + where: { publicId: resolvedProjectPublicId }, + }); + if (!project) { + ctx.status = 400; + ctx.body = { error: 'Invalid project ID' }; + return; + } + + try { + const actor = await createActor({ + projectId: project.id, + name: body.name, + type: body.type, + externalId: body.externalId, + }); + + ctx.status = 201; + ctx.body = actor; + } catch (error) { + if ( + (error as { name?: string }).name === 'SequelizeUniqueConstraintError' + ) { + ctx.status = 409; + ctx.body = { + error: 'An actor with this externalId already exists in the project', + }; + return; + } + throw error; + } +}); + +/** + * @openapi + * /actors/{id}: + * delete: + * tags: + * - Actors + * summary: Delete an actor + * description: Deletes an actor by its ID + * operationId: deleteActor + * parameters: + * - name: id + * in: path + * required: true + * description: Actor ID + * schema: + * type: string + * example: 'act_V1StGXR8Z5jdHi6B' + * responses: + * '204': + * description: Actor deleted + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Actor not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +actorsRouter.delete('/actors/:id', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const actor = await getActor({ id: ctx.params.id }); + + if (!actor) { + ctx.status = 404; + ctx.body = { error: 'Actor not found' }; + return; + } + + const srnDel = buildSrn({ + projectPublicId: actor.projectId!, + resourceType: 'actor', + resourceId: actor.id, + }); + const contextDel: Record = { 'soat:ResourceType': 'actor' }; + if (actor.tags) { + for (const [k, v] of Object.entries(actor.tags)) { + contextDel[`soat:ResourceTag/${k}`] = v as string; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: actor.projectId!, + action: 'actors:DeleteActor', + resource: srnDel, + context: contextDel, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + await deleteActor({ id: ctx.params.id }); + ctx.status = 204; +}); + +/** + * @openapi + * /actors/{id}: + * patch: + * tags: + * - Actors + * summary: Update an actor + * description: Updates an actor's name, type, or externalId + * operationId: updateActor + * parameters: + * - name: id + * in: path + * required: true + * description: Actor ID + * schema: + * type: string + * example: 'act_V1StGXR8Z5jdHi6B' + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * name: + * type: string + * example: 'Updated Actor' + * type: + * type: string + * example: 'assistant' + * externalId: + * type: string + * example: '+15551234567' + * responses: + * '200': + * description: Actor updated + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ActorRecord' + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + * '404': + * description: Actor not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +actorsRouter.patch('/actors/:id', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const actor = await getActor({ id: ctx.params.id }); + + if (!actor) { + ctx.status = 404; + ctx.body = { error: 'Actor not found' }; + return; + } + + const srnUpd = buildSrn({ + projectPublicId: actor.projectId!, + resourceType: 'actor', + resourceId: actor.id, + }); + const contextUpd: Record = { 'soat:ResourceType': 'actor' }; + if (actor.tags) { + for (const [k, v] of Object.entries(actor.tags)) { + contextUpd[`soat:ResourceTag/${k}`] = v as string; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: actor.projectId!, + action: 'actors:UpdateActor', + resource: srnUpd, + context: contextUpd, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const body = ctx.request.body as { + name?: string; + type?: string; + externalId?: string; + }; + + const updated = await updateActor({ + id: ctx.params.id, + name: body.name, + type: body.type, + externalId: body.externalId, + }); + + ctx.body = updated; +}); + +/** + * @openapi + * /actors/{id}/tags: + * get: + * tags: + * - Actors + * summary: Get actor tags + * operationId: getActorTagsRoute + * parameters: + * - name: id + * in: path + * required: true + * schema: + * type: string + * responses: + * '200': + * description: Actor tags + * content: + * application/json: + * schema: + * type: object + * additionalProperties: + * type: string + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + * '404': + * description: Actor not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +actorsRouter.get('/actors/:id/tags', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const actor = await getActor({ id: ctx.params.id }); + + if (!actor) { + ctx.status = 404; + ctx.body = { error: 'Actor not found' }; + return; + } + + const srn = buildSrn({ + projectPublicId: actor.projectId!, + resourceType: 'actor', + resourceId: actor.id, + }); + const context: Record = { 'soat:ResourceType': 'actor' }; + if (actor.tags) { + for (const [k, v] of Object.entries(actor.tags)) { + context[`soat:ResourceTag/${k}`] = v as string; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: actor.projectId!, + action: 'actors:GetActor', + resource: srn, + context, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + ctx.body = await getActorTags({ id: ctx.params.id }); +}); + +/** + * @openapi + * /actors/{id}/tags: + * put: + * tags: + * - Actors + * summary: Replace actor tags + * operationId: putActorTags + * parameters: + * - name: id + * in: path + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * additionalProperties: + * type: string + * responses: + * '200': + * description: Tags replaced + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ActorRecord' + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + * '404': + * description: Actor not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +actorsRouter.put('/actors/:id/tags', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const actor = await getActor({ id: ctx.params.id }); + + if (!actor) { + ctx.status = 404; + ctx.body = { error: 'Actor not found' }; + return; + } + + const srn = buildSrn({ + projectPublicId: actor.projectId!, + resourceType: 'actor', + resourceId: actor.id, + }); + const context: Record = { 'soat:ResourceType': 'actor' }; + if (actor.tags) { + for (const [k, v] of Object.entries(actor.tags)) { + context[`soat:ResourceTag/${k}`] = v as string; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: actor.projectId!, + action: 'actors:UpdateActor', + resource: srn, + context, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const tags = ctx.request.body as Record; + ctx.body = await updateActorTags({ id: ctx.params.id, tags, merge: false }); +}); + +/** + * @openapi + * /actors/{id}/tags: + * patch: + * tags: + * - Actors + * summary: Merge actor tags + * operationId: patchActorTags + * parameters: + * - name: id + * in: path + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * additionalProperties: + * type: string + * responses: + * '200': + * description: Tags merged + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ActorRecord' + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + * '404': + * description: Actor not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +actorsRouter.patch('/actors/:id/tags', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const actor = await getActor({ id: ctx.params.id }); + + if (!actor) { + ctx.status = 404; + ctx.body = { error: 'Actor not found' }; + return; + } + + const srn = buildSrn({ + projectPublicId: actor.projectId!, + resourceType: 'actor', + resourceId: actor.id, + }); + const context: Record = { 'soat:ResourceType': 'actor' }; + if (actor.tags) { + for (const [k, v] of Object.entries(actor.tags)) { + context[`soat:ResourceTag/${k}`] = v as string; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: actor.projectId!, + action: 'actors:UpdateActor', + resource: srn, + context, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const tags = ctx.request.body as Record; + ctx.body = await updateActorTags({ id: ctx.params.id, tags, merge: true }); +}); + +export { actorsRouter }; diff --git a/packages/server/src/rest/v1/agents.ts b/packages/server/src/rest/v1/agents.ts new file mode 100644 index 00000000..f392da82 --- /dev/null +++ b/packages/server/src/rest/v1/agents.ts @@ -0,0 +1,93 @@ +import { Router } from '@ttoss/http-server'; +import type { Context } from 'src/Context'; +import { streamAgent } from 'src/lib/agents'; + +const agentsRouter = new Router(); + +/** + * @openapi + * /agents/run/stream: + * post: + * tags: + * - Agents + * summary: Stream an agent response via SSE + * description: Streams a text response from the agent using Server-Sent Events. Requires authentication. + * operationId: runAgentStream + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - prompt + * properties: + * model: + * type: string + * description: Model name to use. Defaults to AGENT_MODEL env var. + * example: qwen2.5:0.5b + * prompt: + * type: string + * description: The user prompt to send to the agent. + * example: Hello, who are you? + * responses: + * '200': + * description: SSE stream of text chunks + * content: + * text/event-stream: + * schema: + * type: string + * '400': + * description: Missing required fields + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + * '401': + * $ref: '#/components/responses/Unauthorized' + */ +agentsRouter.post('/agents/run/stream', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const body = ctx.request.body as { model?: string; prompt?: string }; + const model = body.model ?? process.env.AGENT_MODEL; + + if (!model || !body.prompt) { + ctx.status = 400; + ctx.body = { + error: + 'prompt is required, and model must be provided or set via AGENT_MODEL', + }; + return; + } + + ctx.respond = false; + ctx.res.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + }); + + try { + const stream = await streamAgent({ model, prompt: body.prompt }); + for await (const chunk of stream) { + const text = chunk.message.content; + if (text) { + ctx.res.write(`data: ${JSON.stringify({ text })}\n\n`); + } + } + ctx.res.write('event: done\ndata: {}\n\n'); + } catch (error) { + ctx.res.write( + `event: error\ndata: ${JSON.stringify({ error: String(error) })}\n\n` + ); + } finally { + ctx.res.end(); + } +}); + +export { agentsRouter }; diff --git a/packages/server/src/rest/v1/aiProviders.ts b/packages/server/src/rest/v1/aiProviders.ts new file mode 100644 index 00000000..97c37bea --- /dev/null +++ b/packages/server/src/rest/v1/aiProviders.ts @@ -0,0 +1,508 @@ +import { Router } from '@ttoss/http-server'; +import type { AiProviderSlug } from '@soat/postgresdb'; +import { AI_PROVIDER_SLUGS } from '@soat/postgresdb'; +import type { Context } from 'src/Context'; +import { db } from 'src/db'; +import { + createAiProvider, + deleteAiProvider, + getAiProvider, + listAiProviders, + updateAiProvider, +} from 'src/lib/aiProviders'; + +const aiProvidersRouter = new Router(); + +/** + * @openapi + * /ai-providers: + * get: + * tags: + * - AI Providers + * summary: List AI providers + * description: Returns all AI providers in the project. + * operationId: listAiProviders + * parameters: + * - name: projectId + * in: query + * required: false + * description: Project ID + * schema: + * type: string + * example: 'proj_V1StGXR8Z5jdHi6B' + * responses: + * '200': + * description: List of AI providers + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/AiProviderRecord' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +aiProvidersRouter.get('/ai-providers', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const projectPublicId = ctx.query.projectId as string | undefined; + + const projectIds = await ctx.authUser.resolveProjectIds({ + projectPublicId, + action: 'aiProviders:ListAiProviders', + }); + + if (projectIds === null) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + ctx.body = await listAiProviders({ projectIds: projectIds ?? [] }); +}); + +/** + * @openapi + * /ai-providers/{aiProviderId}: + * get: + * tags: + * - AI Providers + * summary: Get an AI provider by ID + * description: Returns AI provider details. The secret value is never returned. + * operationId: getAiProvider + * parameters: + * - name: aiProviderId + * in: path + * required: true + * description: AI Provider ID + * schema: + * type: string + * example: 'aip_V1StGXR8Z5jdHi6B' + * responses: + * '200': + * description: AI provider found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/AiProviderRecord' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: AI provider not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +aiProvidersRouter.get('/ai-providers/:aiProviderId', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const provider = await getAiProvider({ id: ctx.params.aiProviderId }); + if (!provider) { + ctx.status = 404; + ctx.body = { error: 'AI provider not found' }; + return; + } + + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: provider.projectId!, + action: 'aiProviders:GetAiProvider', + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + ctx.body = provider; +}); + +/** + * @openapi + * /ai-providers: + * post: + * tags: + * - AI Providers + * summary: Create an AI provider + * description: Creates a new AI provider configuration. + * operationId: createAiProvider + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - name + * - provider + * - defaultModel + * properties: + * projectId: + * type: string + * description: Project ID. Required for JWT auth; omit when using a project key. + * example: 'proj_V1StGXR8Z5jdHi6B' + * secretId: + * type: string + * description: Secret ID containing the provider credentials + * example: 'sec_V1StGXR8Z5jdHi6B' + * name: + * type: string + * example: 'OpenAI Production' + * provider: + * type: string + * enum: [openai, anthropic, google, xai, groq, ollama, azure, bedrock, gateway, custom] + * example: 'openai' + * defaultModel: + * type: string + * example: 'gpt-4o' + * baseUrl: + * type: string + * example: 'https://api.openai.com/v1' + * config: + * type: object + * description: Provider-specific configuration + * responses: + * '201': + * description: AI provider created + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/AiProviderRecord' + * '400': + * description: Invalid request body + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +aiProvidersRouter.post('/ai-providers', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const body = ctx.request.body as { + projectId?: string; + secretId?: string; + name?: string; + provider?: string; + defaultModel?: string; + baseUrl?: string; + config?: Record; + }; + + if (!body.name) { + ctx.status = 400; + ctx.body = { error: 'name is required' }; + return; + } + if ( + !body.provider || + !AI_PROVIDER_SLUGS.includes(body.provider as AiProviderSlug) + ) { + ctx.status = 400; + ctx.body = { + error: `provider must be one of: ${AI_PROVIDER_SLUGS.join(', ')}`, + }; + return; + } + if (!body.defaultModel) { + ctx.status = 400; + ctx.body = { error: 'defaultModel is required' }; + return; + } + + let resolvedProjectPublicId = body.projectId; + if (!resolvedProjectPublicId) { + if (ctx.authUser.projectKeyProjectId) { + resolvedProjectPublicId = ctx.authUser.projectKeyProjectId; + } else { + ctx.status = 400; + ctx.body = { error: 'projectId is required' }; + return; + } + } + + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: resolvedProjectPublicId, + action: 'aiProviders:CreateAiProvider', + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const project = await db.Project.findOne({ + where: { publicId: resolvedProjectPublicId }, + }); + if (!project) { + ctx.status = 400; + ctx.body = { error: 'Invalid project ID' }; + return; + } + + let resolvedSecretId: number | undefined; + if (body.secretId) { + const secret = await db.Secret.findOne({ + where: { publicId: body.secretId, projectId: project.id }, + }); + if (!secret) { + ctx.status = 400; + ctx.body = { error: 'Invalid secret ID' }; + return; + } + resolvedSecretId = secret.id; + } + + const provider = await createAiProvider({ + projectId: project.id, + secretId: resolvedSecretId, + name: body.name, + provider: body.provider as AiProviderSlug, + defaultModel: body.defaultModel, + baseUrl: body.baseUrl, + config: body.config, + }); + + ctx.status = 201; + ctx.body = provider; +}); + +/** + * @openapi + * /ai-providers/{aiProviderId}: + * patch: + * tags: + * - AI Providers + * summary: Update an AI provider + * description: Updates the configuration of an AI provider. + * operationId: updateAiProvider + * parameters: + * - name: aiProviderId + * in: path + * required: true + * description: AI Provider ID + * schema: + * type: string + * example: 'aip_V1StGXR8Z5jdHi6B' + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * secretId: + * type: string + * name: + * type: string + * provider: + * type: string + * enum: [openai, anthropic, google, xai, groq, ollama, azure, bedrock, gateway, custom] + * defaultModel: + * type: string + * baseUrl: + * type: string + * config: + * type: object + * responses: + * '200': + * description: AI provider updated + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/AiProviderRecord' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: AI provider not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +aiProvidersRouter.patch('/ai-providers/:aiProviderId', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const existing = await getAiProvider({ id: ctx.params.aiProviderId }); + if (!existing) { + ctx.status = 404; + ctx.body = { error: 'AI provider not found' }; + return; + } + + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: existing.projectId!, + action: 'aiProviders:UpdateAiProvider', + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const body = ctx.request.body as { + secretId?: string; + name?: string; + provider?: string; + defaultModel?: string; + baseUrl?: string | null; + config?: Record | null; + }; + + let resolvedSecretId: number | undefined; + if (body.secretId !== undefined) { + const project = await db.Project.findOne({ + where: { publicId: existing.projectId! }, + }); + const secret = await db.Secret.findOne({ + where: { publicId: body.secretId, projectId: project!.id }, + }); + if (!secret) { + ctx.status = 400; + ctx.body = { error: 'Invalid secret ID' }; + return; + } + resolvedSecretId = secret.id; + } + + const updated = await updateAiProvider({ + id: ctx.params.aiProviderId, + secretId: resolvedSecretId, + name: body.name, + provider: body.provider as AiProviderSlug | undefined, + defaultModel: body.defaultModel, + baseUrl: body.baseUrl, + config: body.config, + }); + + ctx.body = updated; +}); + +/** + * @openapi + * /ai-providers/{aiProviderId}: + * delete: + * tags: + * - AI Providers + * summary: Delete an AI provider + * description: Deletes an AI provider. + * operationId: deleteAiProvider + * parameters: + * - name: aiProviderId + * in: path + * required: true + * description: AI Provider ID + * schema: + * type: string + * example: 'aip_V1StGXR8Z5jdHi6B' + * responses: + * '204': + * description: AI provider deleted + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: AI provider not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +aiProvidersRouter.delete( + '/ai-providers/:aiProviderId', + async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const existing = await getAiProvider({ id: ctx.params.aiProviderId }); + if (!existing) { + ctx.status = 404; + ctx.body = { error: 'AI provider not found' }; + return; + } + + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: existing.projectId!, + action: 'aiProviders:DeleteAiProvider', + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + await deleteAiProvider({ id: ctx.params.aiProviderId }); + ctx.status = 204; + } +); + +export { aiProvidersRouter }; diff --git a/packages/server/src/rest/v1/conversations.ts b/packages/server/src/rest/v1/conversations.ts new file mode 100644 index 00000000..faa8278a --- /dev/null +++ b/packages/server/src/rest/v1/conversations.ts @@ -0,0 +1,1255 @@ +import { Router } from '@ttoss/http-server'; +import type { Context } from 'src/Context'; +import { db } from 'src/db'; +import { + addConversationMessage, + createConversation, + deleteConversation, + getConversation, + getConversationTags, + listConversationActors, + listConversationMessages, + listConversations, + removeConversationMessage, + updateConversationStatus, + updateConversationTags, +} from 'src/lib/conversations'; +import { buildSrn } from 'src/lib/iam'; + +const conversationsRouter = new Router(); + +/** + * @openapi + * /conversations: + * get: + * tags: + * - Conversations + * summary: List conversations + * description: Returns all conversations the caller has access to. If projectId is provided, returns only conversations in that project. project keys are scoped to a single project automatically. + * operationId: listConversations + * parameters: + * - name: projectId + * in: query + * required: false + * description: Project ID (optional) + * schema: + * type: string + * example: 'proj_V1StGXR8Z5jdHi6B' + * - name: actorId + * in: query + * required: false + * description: Filter by actor ID + * schema: + * type: string + * example: 'act_V1StGXR8Z5jdHi6B' + * - name: limit + * in: query + * required: false + * description: Maximum number of results to return (default 50) + * schema: + * type: integer + * example: 50 + * - name: offset + * in: query + * required: false + * description: Number of results to skip (default 0) + * schema: + * type: integer + * example: 0 + * responses: + * '200': + * description: List of conversations + * content: + * application/json: + * schema: + * type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/ConversationRecord' + * total: + * type: integer + * limit: + * type: integer + * offset: + * type: integer + * required: false + * description: Number of results to skip (default 0) + * schema: + * type: integer + * example: 0 + * responses: + * '200': + * description: List of conversations + * content: + * application/json: + * schema: + * type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/ConversationRecord' + * total: + * type: integer + * limit: + * type: integer + * offset: + const limit = ctx.query.limit ? parseInt(ctx.query.limit as string, 10) : undefined; + const offset = ctx.query.offset ? parseInt(ctx.query.offset as string, 10) : undefined; + + const projectIds = await ctx.authUser.resolveProjectIds({ + projectPublicId, + action: 'conversations:ListConversations', + }); + + if (projectIds === null) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + ctx.body = await listConversations({ projectIds, actorId, limit, offset' + */ +conversationsRouter.get('/conversations', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const projectPublicId = ctx.query.projectId as string | undefined; + const actorId = ctx.query.actorId as string | undefined; + const limit = ctx.query.limit + ? parseInt(ctx.query.limit as string, 10) + : undefined; + const offset = ctx.query.offset + ? parseInt(ctx.query.offset as string, 10) + : undefined; + + const projectIds = await ctx.authUser.resolveProjectIds({ + projectPublicId, + action: 'conversations:ListConversations', + }); + + if (projectIds === null) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + ctx.body = await listConversations({ projectIds, actorId, limit, offset }); +}); + +/** + * @openapi + * /conversations/{id}: + * get: + * tags: + * - Conversations + * summary: Get a conversation by ID + * description: Returns a conversation by its ID + * operationId: getConversation + * parameters: + * - name: id + * in: path + * required: true + * description: Conversation ID + * schema: + * type: string + * example: 'conv_V1StGXR8Z5jdHi6B' + * responses: + * '200': + * description: Conversation found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ConversationRecord' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Conversation not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +conversationsRouter.get('/conversations/:id', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const conversation = await getConversation({ id: ctx.params.id }); + + if (!conversation) { + ctx.status = 404; + ctx.body = { error: 'Conversation not found' }; + return; + } + + const srnGet = buildSrn({ + projectPublicId: conversation.projectId!, + resourceType: 'conversation', + resourceId: conversation.id, + }); + const contextGet: Record = { + 'soat:ResourceType': 'conversation', + }; + if (conversation.tags) { + for (const [k, v] of Object.entries(conversation.tags)) { + contextGet[`soat:ResourceTag/${k}`] = v as string; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: conversation.projectId!, + action: 'conversations:GetConversation', + resource: srnGet, + context: contextGet, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + ctx.body = conversation; +}); + +/** + * @openapi + * /conversations: + * post: + * tags: + * - Conversations + * summary: Create a conversation + * description: Creates a new conversation. project keys automatically infer the project from the key's scope; JWT callers must supply projectId. + * operationId: createConversation + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * projectId: + * type: string + * description: Project ID. Required for JWT auth; omit when using an project key. + * example: 'proj_V1StGXR8Z5jdHi6B' + * status: + * type: string + * enum: [open, closed] + * default: open + * description: Initial conversation status + * responses: + * '201': + * description: Conversation created + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ConversationRecord' + * '400': + * description: Invalid request body + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +conversationsRouter.post('/conversations', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const body = ctx.request.body as { + projectId?: string; + status?: string; + }; + + let resolvedProjectPublicId = body.projectId; + if (!resolvedProjectPublicId) { + if (ctx.authUser.projectKeyProjectId) { + resolvedProjectPublicId = ctx.authUser.projectKeyProjectId; + } else { + ctx.status = 400; + ctx.body = { error: 'projectId is required' }; + return; + } + } + + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: resolvedProjectPublicId, + action: 'conversations:CreateConversation', + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const project = await db.Project.findOne({ + where: { publicId: resolvedProjectPublicId }, + }); + if (!project) { + ctx.status = 400; + ctx.body = { error: 'Invalid project ID' }; + return; + } + + const conversation = await createConversation({ + projectId: project.id, + status: body.status, + }); + + ctx.status = 201; + ctx.body = conversation; +}); + +/** + * @openapi + * /conversations/{id}: + * patch: + * tags: + * - Conversations + * summary: Update a conversation + * description: Updates the status of a conversation + * operationId: updateConversation + * parameters: + * - name: id + * in: path + * required: true + * description: Conversation ID + * schema: + * type: string + * example: 'conv_V1StGXR8Z5jdHi6B' + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - status + * properties: + * status: + * type: string + * enum: [open, closed] + * description: New conversation status + * responses: + * '200': + * description: Conversation updated + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ConversationRecord' + * '400': + * description: Invalid request body + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Conversation not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +conversationsRouter.patch('/conversations/:id', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const body = ctx.request.body as { status: string }; + + if (!body.status) { + ctx.status = 400; + ctx.body = { error: 'status is required' }; + return; + } + + const conversation = await getConversation({ id: ctx.params.id }); + + if (!conversation) { + ctx.status = 404; + ctx.body = { error: 'Conversation not found' }; + return; + } + + const srnUpd = buildSrn({ + projectPublicId: conversation.projectId!, + resourceType: 'conversation', + resourceId: conversation.id, + }); + const contextUpd: Record = { + 'soat:ResourceType': 'conversation', + }; + if (conversation.tags) { + for (const [k, v] of Object.entries(conversation.tags)) { + contextUpd[`soat:ResourceTag/${k}`] = v as string; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: conversation.projectId!, + action: 'conversations:UpdateConversation', + resource: srnUpd, + context: contextUpd, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const updated = await updateConversationStatus({ + id: ctx.params.id, + status: body.status, + }); + + ctx.body = updated; +}); + +/** + * @openapi + * /conversations/{id}: + * delete: + * tags: + * - Conversations + * summary: Delete a conversation + * description: Deletes a conversation by its ID + * operationId: deleteConversation + * parameters: + * - name: id + * in: path + * required: true + * description: Conversation ID + * schema: + * type: string + * example: 'conv_V1StGXR8Z5jdHi6B' + * responses: + * '204': + * description: Conversation deleted + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Conversation not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +conversationsRouter.delete('/conversations/:id', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const conversation = await getConversation({ id: ctx.params.id }); + + if (!conversation) { + ctx.status = 404; + ctx.body = { error: 'Conversation not found' }; + return; + } + + const srnDel = buildSrn({ + projectPublicId: conversation.projectId!, + resourceType: 'conversation', + resourceId: conversation.id, + }); + const contextDel: Record = { + 'soat:ResourceType': 'conversation', + }; + if (conversation.tags) { + for (const [k, v] of Object.entries(conversation.tags)) { + contextDel[`soat:ResourceTag/${k}`] = v as string; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: conversation.projectId!, + action: 'conversations:DeleteConversation', + resource: srnDel, + context: contextDel, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + await deleteConversation({ id: ctx.params.id }); + + ctx.status = 204; +}); + +/** + * @openapi + * /conversations/{id}/messages: + * get: + * tags: + * - Conversations + * summary: List conversation messages + * description: Returns all messages (documents) attached to a conversation, ordered by position + * operationId: listConversationMessages + * parameters: + * - name: id + * in: path + * required: true + * description: Conversation ID + * schema: + * type: string + * example: 'conv_V1StGXR8Z5jdHi6B' + * - name: limit + * in: query + * required: false + * description: Maximum number of results to return (default 50) + * schema: + * type: integer + * example: 50 + * - name: offset + * in: query + * required: false + * description: Number of results to skip (default 0) + * schema: + * type: integer + * example: 0 + * responses: + * '200': + * description: List of messages + * content: + * application/json: + * schema: + * type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/ConversationMessageRecord' + * total: + * type: integer + * limit: + * type: integer + * offset: + * type: integer + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Conversation not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +conversationsRouter.get('/conversations/:id/messages', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const conversation = await getConversation({ id: ctx.params.id }); + + if (!conversation) { + ctx.status = 404; + ctx.body = { error: 'Conversation not found' }; + return; + } + + const srnMsgs = buildSrn({ + projectPublicId: conversation.projectId!, + resourceType: 'conversation', + resourceId: conversation.id, + }); + const contextMsgs: Record = { + 'soat:ResourceType': 'conversation', + }; + if (conversation.tags) { + for (const [k, v] of Object.entries(conversation.tags)) { + contextMsgs[`soat:ResourceTag/${k}`] = v as string; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: conversation.projectId!, + action: 'conversations:GetConversation', + resource: srnMsgs, + context: contextMsgs, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const limit = ctx.query.limit + ? parseInt(ctx.query.limit as string, 10) + : undefined; + const offset = ctx.query.offset + ? parseInt(ctx.query.offset as string, 10) + : undefined; + + const messages = await listConversationMessages({ + conversationId: ctx.params.id, + limit, + offset, + }); + + ctx.body = messages; +}); + +/** + * @openapi + * /conversations/{id}/messages: + * post: + * tags: + * - Conversations + * summary: Add a message to a conversation + * description: Creates a document from the message text and attaches it to the conversation at the given position. If position is omitted, it is appended at the end. + * operationId: addConversationMessage + * parameters: + * - name: id + * in: path + * required: true + * description: Conversation ID + * schema: + * type: string + * example: 'conv_V1StGXR8Z5jdHi6B' + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - message + * - actorId + * properties: + * message: + * type: string + * description: Message text content to add to the conversation + * example: 'Hello, how can I help you?' + * actorId: + * type: string + * description: Actor ID who is sending this message + * example: 'act_V1StGXR8Z5jdHi6B' + * position: + * type: integer + * description: Zero-based position. Defaults to MAX+1 (append). + * example: 0 + * responses: + * '201': + * description: Message added + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ConversationMessageRecord' + * '400': + * description: Invalid request body + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Conversation or actor not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +conversationsRouter.post( + '/conversations/:id/messages', + async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const body = ctx.request.body as { + message: string; + actorId: string; + position?: number; + }; + + if (!body.message) { + ctx.status = 400; + ctx.body = { error: 'message is required' }; + return; + } + + if (!body.actorId) { + ctx.status = 400; + ctx.body = { error: 'actorId is required' }; + return; + } + + const conversation = await getConversation({ id: ctx.params.id }); + + if (!conversation) { + ctx.status = 404; + ctx.body = { error: 'Conversation not found' }; + return; + } + + const srnAddMsg = buildSrn({ + projectPublicId: conversation.projectId!, + resourceType: 'conversation', + resourceId: conversation.id, + }); + const contextAddMsg: Record = { + 'soat:ResourceType': 'conversation', + }; + if (conversation.tags) { + for (const [k, v] of Object.entries(conversation.tags)) { + contextAddMsg[`soat:ResourceTag/${k}`] = v as string; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: conversation.projectId!, + action: 'conversations:UpdateConversation', + resource: srnAddMsg, + context: contextAddMsg, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const message = await addConversationMessage({ + conversationId: ctx.params.id, + message: body.message, + actorId: body.actorId, + position: body.position, + }); + + if (!message) { + ctx.status = 404; + ctx.body = { error: 'Conversation or actor not found' }; + return; + } + + ctx.status = 201; + ctx.body = message; + } +); + +/** + * @openapi + * /conversations/{id}/messages/{documentId}: + * delete: + * tags: + * - Conversations + * summary: Remove a message from a conversation + * description: Removes a document from a conversation + * operationId: removeConversationMessage + * parameters: + * - name: id + * in: path + * required: true + * description: Conversation ID + * schema: + * type: string + * example: 'conv_V1StGXR8Z5jdHi6B' + * - name: documentId + * in: path + * required: true + * description: Document ID + * schema: + * type: string + * example: 'doc_V1StGXR8Z5jdHi6B' + * responses: + * '204': + * description: Message removed + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Conversation or message not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +conversationsRouter.delete( + '/conversations/:id/messages/:documentId', + async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const conversation = await getConversation({ id: ctx.params.id }); + + if (!conversation) { + ctx.status = 404; + ctx.body = { error: 'Conversation not found' }; + return; + } + + const srnRmMsg = buildSrn({ + projectPublicId: conversation.projectId!, + resourceType: 'conversation', + resourceId: conversation.id, + }); + const contextRmMsg: Record = { + 'soat:ResourceType': 'conversation', + }; + if (conversation.tags) { + for (const [k, v] of Object.entries(conversation.tags)) { + contextRmMsg[`soat:ResourceTag/${k}`] = v as string; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: conversation.projectId!, + action: 'conversations:UpdateConversation', + resource: srnRmMsg, + context: contextRmMsg, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const result = await removeConversationMessage({ + conversationId: ctx.params.id, + documentId: ctx.params.documentId, + }); + + if (!result) { + ctx.status = 404; + ctx.body = { error: 'Message not found' }; + return; + } + + ctx.status = 204; + } +); + +/** + * @openapi + * /conversations/{id}/actors: + * get: + * tags: + * - Conversations + * summary: List actors in a conversation + * description: Returns all distinct actors who have sent at least one message in the conversation + * operationId: listConversationActors + * parameters: + * - name: id + * in: path + * required: true + * description: Conversation ID + * schema: + * type: string + * example: 'conv_V1StGXR8Z5jdHi6B' + * responses: + * '200': + * description: List of actors + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/ConversationActorRecord' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Conversation not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +conversationsRouter.get('/conversations/:id/actors', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const conversation = await getConversation({ id: ctx.params.id }); + + if (!conversation) { + ctx.status = 404; + ctx.body = { error: 'Conversation not found' }; + return; + } + + const srnActors = buildSrn({ + projectPublicId: conversation.projectId!, + resourceType: 'conversation', + resourceId: conversation.id, + }); + const contextActors: Record = { + 'soat:ResourceType': 'conversation', + }; + if (conversation.tags) { + for (const [k, v] of Object.entries(conversation.tags)) { + contextActors[`soat:ResourceTag/${k}`] = v as string; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: conversation.projectId!, + action: 'conversations:GetConversation', + resource: srnActors, + context: contextActors, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const actors = await listConversationActors({ + conversationId: ctx.params.id, + }); + ctx.body = actors; +}); + +/** + * @openapi + * /conversations/{id}/tags: + * get: + * tags: + * - Conversations + * summary: Get conversation tags + * operationId: getConversationTagsRoute + * parameters: + * - name: id + * in: path + * required: true + * schema: + * type: string + * responses: + * '200': + * description: Conversation tags + * content: + * application/json: + * schema: + * type: object + * additionalProperties: + * type: string + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + * '404': + * description: Conversation not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +conversationsRouter.get('/conversations/:id/tags', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const conversation = await getConversation({ id: ctx.params.id }); + + if (!conversation) { + ctx.status = 404; + ctx.body = { error: 'Conversation not found' }; + return; + } + + const srn = buildSrn({ + projectPublicId: conversation.projectId!, + resourceType: 'conversation', + resourceId: conversation.id, + }); + const context: Record = { + 'soat:ResourceType': 'conversation', + }; + if (conversation.tags) { + for (const [k, v] of Object.entries(conversation.tags)) { + context[`soat:ResourceTag/${k}`] = v as string; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: conversation.projectId!, + action: 'conversations:GetConversation', + resource: srn, + context, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + ctx.body = await getConversationTags({ id: ctx.params.id }); +}); + +/** + * @openapi + * /conversations/{id}/tags: + * put: + * tags: + * - Conversations + * summary: Replace conversation tags + * operationId: putConversationTags + * parameters: + * - name: id + * in: path + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * additionalProperties: + * type: string + * responses: + * '200': + * description: Tags replaced + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ConversationRecord' + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + * '404': + * description: Conversation not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +conversationsRouter.put('/conversations/:id/tags', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const conversation = await getConversation({ id: ctx.params.id }); + + if (!conversation) { + ctx.status = 404; + ctx.body = { error: 'Conversation not found' }; + return; + } + + const srn = buildSrn({ + projectPublicId: conversation.projectId!, + resourceType: 'conversation', + resourceId: conversation.id, + }); + const context: Record = { + 'soat:ResourceType': 'conversation', + }; + if (conversation.tags) { + for (const [k, v] of Object.entries(conversation.tags)) { + context[`soat:ResourceTag/${k}`] = v as string; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: conversation.projectId!, + action: 'conversations:UpdateConversation', + resource: srn, + context, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const tags = ctx.request.body as Record; + ctx.body = await updateConversationTags({ + id: ctx.params.id, + tags, + merge: false, + }); +}); + +/** + * @openapi + * /conversations/{id}/tags: + * patch: + * tags: + * - Conversations + * summary: Merge conversation tags + * operationId: patchConversationTags + * parameters: + * - name: id + * in: path + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * additionalProperties: + * type: string + * responses: + * '200': + * description: Tags merged + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ConversationRecord' + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + * '404': + * description: Conversation not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +conversationsRouter.patch('/conversations/:id/tags', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const conversation = await getConversation({ id: ctx.params.id }); + + if (!conversation) { + ctx.status = 404; + ctx.body = { error: 'Conversation not found' }; + return; + } + + const srn = buildSrn({ + projectPublicId: conversation.projectId!, + resourceType: 'conversation', + resourceId: conversation.id, + }); + const context: Record = { + 'soat:ResourceType': 'conversation', + }; + if (conversation.tags) { + for (const [k, v] of Object.entries(conversation.tags)) { + context[`soat:ResourceTag/${k}`] = v as string; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: conversation.projectId!, + action: 'conversations:UpdateConversation', + resource: srn, + context, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const tags = ctx.request.body as Record; + ctx.body = await updateConversationTags({ + id: ctx.params.id, + tags, + merge: true, + }); +}); + +export { conversationsRouter }; diff --git a/packages/server/src/rest/v1/documents.ts b/packages/server/src/rest/v1/documents.ts index c6e44c06..f43b0f08 100644 --- a/packages/server/src/rest/v1/documents.ts +++ b/packages/server/src/rest/v1/documents.ts @@ -1,278 +1,911 @@ +import { Router } from '@ttoss/http-server'; +import type { Context } from 'src/Context'; +import { db } from 'src/db'; import { createDocument, deleteDocument, - type EmbeddingConfig, getDocument, + getDocumentTags, listDocuments, - searchDocumentsBySimilarity, - type StorageConfig, + searchDocuments, updateDocument, -} from '@soat/documents-core'; -import { getConfigFromEnv } from '@soat/embeddings-core'; -import { Router } from '@ttoss/http-server'; + updateDocumentTags, +} from 'src/lib/documents'; +import { buildSrn } from 'src/lib/iam'; + +const documentsRouter = new Router(); -import type { Context } from '../../Context'; - -const defaultStorageConfig: StorageConfig = { - type: 'local', - local: { - path: '/tmp/documents', - }, -}; - -const getEmbeddingConfig = (): EmbeddingConfig | undefined => { - try { - return getConfigFromEnv(); - } catch { - return undefined; - } -}; - -const documentsRouter = new Router(); - -documentsRouter.get('/', async (ctx: Context) => { - try { - const documents = await listDocuments(); - ctx.status = 200; - ctx.body = { success: true, documents }; - } catch (error) { - ctx.status = 500; - ctx.body = { - success: false, - error: error instanceof Error ? error.message : 'Unknown error', - }; +/** + * @openapi + * /documents: + * get: + * tags: + * - Documents + * summary: List documents + * description: Returns all documents the caller has access to. If projectId is provided, returns only documents in that project. project keys are scoped to a single project automatically. JWT users without projectId receive documents across all their accessible projects. + * operationId: listDocuments + * parameters: + * - name: projectId + * in: query + * required: false + * description: Project ID (optional) + * schema: + * type: string + * example: 'proj_V1StGXR8Z5jdHi6B' + * - name: limit + * in: query + * required: false + * description: Maximum number of results to return (default 50) + * schema: + * type: integer + * example: 50 + * - name: offset + * in: query + * required: false + * description: Number of results to skip (default 0) + * schema: + * type: integer + * example: 0 + * responses: + * '200': + * description: List of documents + * content: + * application/json: + * schema: + * type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/DocumentRecord' + * total: + * type: integer + * limit: + * type: integer + * offset: + * type: integer + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +documentsRouter.get('/documents', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; } -}); -documentsRouter.post('/', async (ctx: Context) => { - try { - const { content, title, metadata, generateEmbedding } = ctx.request.body; - if (!content) { - ctx.status = 400; - ctx.body = { success: false, error: 'Content is required' }; - return; - } + const projectPublicId = ctx.query.projectId as string | undefined; + const limit = ctx.query.limit + ? parseInt(ctx.query.limit as string, 10) + : undefined; + const offset = ctx.query.offset + ? parseInt(ctx.query.offset as string, 10) + : undefined; + + const projectIds = await ctx.authUser!.resolveProjectIds({ + projectPublicId, + action: 'documents:ListDocuments', + }); - const embeddingConfig = getEmbeddingConfig(); - - const document = await createDocument({ - storageConfig: defaultStorageConfig, - embeddingConfig, - content, - options: { - title, - metadata, - generateEmbedding, - }, - }); - - ctx.status = 201; - ctx.body = { - success: true, - document: { - id: document.id, - title: document.title, - fileId: document.fileId, - embeddingModel: document.embeddingModel, - embeddingProvider: document.embeddingProvider, - hasEmbedding: !!document.embedding, - metadata: document.metadata, - createdAt: document.createdAt, - updatedAt: document.updatedAt, - }, - }; - } catch (error) { - ctx.status = 500; - ctx.body = { - success: false, - error: error instanceof Error ? error.message : 'Unknown error', - }; + if (projectIds === null) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; } + + ctx.body = await listDocuments({ projectIds, limit, offset }); }); -documentsRouter.get('/search', async (ctx: Context) => { - try { - const { query, limit, threshold } = ctx.query; - if (!query || typeof query !== 'string') { - ctx.status = 400; - ctx.body = { success: false, error: 'Query is required' }; - return; +/** + * @openapi + * /documents/{id}: + * get: + * tags: + * - Documents + * summary: Get a document by ID + * description: Returns a document with its text content + * operationId: getDocument + * parameters: + * - name: id + * in: path + * required: true + * description: Document ID + * schema: + * type: string + * example: 'doc_V1StGXR8Z5jdHi6B' + * responses: + * '200': + * description: Document found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/DocumentRecord' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Document not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +documentsRouter.get('/documents/:id', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const doc = await getDocument({ id: ctx.params.id }); + + if (!doc) { + ctx.status = 404; + ctx.body = { error: 'Document not found' }; + return; + } + + const srn = buildSrn({ + projectPublicId: doc.projectId!, + resourceType: 'document', + resourceId: doc.id, + }); + const context: Record = { 'soat:ResourceType': 'document' }; + if (doc.tags) { + for (const [k, v] of Object.entries(doc.tags)) { + context[`soat:ResourceTag/${k}`] = v; } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: doc.projectId!, + action: 'documents:GetDocument', + resource: srn, + context, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + ctx.body = doc; +}); + +/** + * @openapi + * /documents: + * post: + * tags: + * - Documents + * summary: Create a document + * description: Creates a new text document with an embedding vector. project keys automatically infer the project from the key's scope; JWT callers must supply projectId. + * operationId: createDocument + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - content + * properties: + * projectId: + * type: string + * description: Project ID. Required for JWT auth; omit when using an project key. + * example: 'proj_V1StGXR8Z5jdHi6B' + * content: + * type: string + * example: 'The quick brown fox jumps over the lazy dog.' + * filename: + * type: string + * example: 'my-doc.txt' + * title: + * type: string + * example: 'My Document' + * metadata: + * type: object + * description: Arbitrary key-value metadata + * tags: + * type: object + * additionalProperties: + * type: string + * description: Key-value tags + * example: { env: 'production' } + * responses: + * '201': + * description: Document created + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/DocumentRecord' + * '400': + * description: Invalid request body + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +documentsRouter.post('/documents', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const body = ctx.request.body as { + projectId?: string; + content: string; + filename?: string; + title?: string; + metadata?: Record; + tags?: Record; + }; - const embeddingConfig = getEmbeddingConfig(); - if (!embeddingConfig) { + if (!body.content) { + ctx.status = 400; + ctx.body = { error: 'content is required' }; + return; + } + + // Resolve projectId: use explicit value, infer from project key, or error for JWT + let resolvedProjectPublicId = body.projectId; + if (!resolvedProjectPublicId) { + if (ctx.authUser.projectKeyProjectId) { + resolvedProjectPublicId = ctx.authUser.projectKeyProjectId; + } else { ctx.status = 400; - ctx.body = { - success: false, - error: - 'Embedding configuration is required for search. Set EMBEDDINGS_OLLAMA_MODEL or EMBEDDINGS_OPENAI_KEY', - }; + ctx.body = { error: 'projectId is required' }; return; } + } - const documents = await searchDocumentsBySimilarity({ - storageConfig: defaultStorageConfig, - embeddingConfig, - query, - options: { - limit: limit ? parseInt(limit as string, 10) : undefined, - threshold: threshold ? parseFloat(threshold as string) : undefined, - }, - }); - - ctx.status = 200; - ctx.body = { - success: true, - documents: documents.map((doc) => { - return { - id: doc.id, - title: doc.title, - fileId: doc.fileId, - content: doc.content?.toString(), - embeddingModel: doc.embeddingModel, - embeddingProvider: doc.embeddingProvider, - metadata: doc.metadata, - createdAt: doc.createdAt, - updatedAt: doc.updatedAt, - }; - }), - }; - } catch (error) { - ctx.status = 500; - ctx.body = { - success: false, - error: error instanceof Error ? error.message : 'Unknown error', - }; + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: resolvedProjectPublicId, + action: 'documents:CreateDocument', + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; } + + const project = await db.Project.findOne({ + where: { publicId: resolvedProjectPublicId }, + }); + if (!project) { + ctx.status = 400; + ctx.body = { error: 'Invalid project ID' }; + return; + } + + const doc = await createDocument({ + projectId: project.id, + content: body.content, + filename: body.filename, + title: body.title, + metadata: body.metadata, + tags: body.tags, + }); + + ctx.status = 201; + ctx.body = doc; }); -documentsRouter.get('/:id', async (ctx: Context) => { - try { - const { id } = ctx.params; - if (!id) { - ctx.status = 400; - ctx.body = { success: false, error: 'ID is required' }; - return; - } +/** + * @openapi + * /documents/{id}: + * delete: + * tags: + * - Documents + * summary: Delete a document + * description: Deletes a document and its underlying file + * operationId: deleteDocument + * parameters: + * - name: id + * in: path + * required: true + * description: Document ID + * schema: + * type: string + * example: 'doc_V1StGXR8Z5jdHi6B' + * responses: + * '204': + * description: Document deleted + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Document not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +documentsRouter.delete('/documents/:id', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } - const document = await getDocument({ - storageConfig: defaultStorageConfig, - id, - }); + const doc = await getDocument({ id: ctx.params.id }); - if (!document) { - ctx.status = 404; - ctx.body = { success: false, error: 'Document not found' }; - return; + if (!doc) { + ctx.status = 404; + ctx.body = { error: 'Document not found' }; + return; + } + + const srnDel = buildSrn({ + projectPublicId: doc.projectId!, + resourceType: 'document', + resourceId: doc.id, + }); + const contextDel: Record = { + 'soat:ResourceType': 'document', + }; + if (doc.tags) { + for (const [k, v] of Object.entries(doc.tags)) { + contextDel[`soat:ResourceTag/${k}`] = v; } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: doc.projectId!, + action: 'documents:DeleteDocument', + resource: srnDel, + context: contextDel, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const result = await deleteDocument({ id: ctx.params.id }); - ctx.status = 200; - ctx.body = { - success: true, - document: { - id: document.id, - title: document.title, - fileId: document.fileId, - content: document.content?.toString(), - embeddingModel: document.embeddingModel, - embeddingProvider: document.embeddingProvider, - hasEmbedding: !!document.embedding, - metadata: document.metadata, - createdAt: document.createdAt, - updatedAt: document.updatedAt, - }, - }; - } catch (error) { - ctx.status = 500; - ctx.body = { - success: false, - error: error instanceof Error ? error.message : 'Unknown error', - }; + if (result === null) { + ctx.status = 404; + ctx.body = { error: 'Document not found' }; + return; } + + ctx.status = 204; }); -documentsRouter.put('/:id', async (ctx: Context) => { - try { - const { id } = ctx.params; - if (!id) { - ctx.status = 400; - ctx.body = { success: false, error: 'ID is required' }; - return; - } +/** + * @openapi + * /documents/{id}: + * patch: + * tags: + * - Documents + * summary: Update a document + * description: Update a document's content, title, metadata, or tags. Updating content re-computes the embedding vector. + * operationId: updateDocument + * parameters: + * - name: id + * in: path + * required: true + * description: Document ID + * schema: + * type: string + * example: 'doc_V1StGXR8Z5jdHi6B' + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * content: + * type: string + * description: New text content (re-embeds the document) + * title: + * type: string + * metadata: + * type: object + * tags: + * type: object + * additionalProperties: + * type: string + * description: Key-value tags + * responses: + * '200': + * description: Document updated + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/DocumentRecord' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Document not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +documentsRouter.patch('/documents/:id', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } - const { content, title, metadata, regenerateEmbedding } = ctx.request.body; - const embeddingConfig = getEmbeddingConfig(); - - const document = await updateDocument({ - storageConfig: defaultStorageConfig, - embeddingConfig, - id, - content, - title, - metadata, - regenerateEmbedding, - }); - - if (!document) { - ctx.status = 404; - ctx.body = { success: false, error: 'Document not found' }; - return; + const doc = await getDocument({ id: ctx.params.id }); + + if (!doc) { + ctx.status = 404; + ctx.body = { error: 'Document not found' }; + return; + } + + const srnUpd = buildSrn({ + projectPublicId: doc.projectId!, + resourceType: 'document', + resourceId: doc.id, + }); + const contextUpd: Record = { + 'soat:ResourceType': 'document', + }; + if (doc.tags) { + for (const [k, v] of Object.entries(doc.tags)) { + contextUpd[`soat:ResourceTag/${k}`] = v; } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: doc.projectId!, + action: 'documents:UpdateDocument', + resource: srnUpd, + context: contextUpd, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const body = ctx.request.body as { + content?: string; + title?: string; + metadata?: Record; + tags?: Record; + }; + + const updated = await updateDocument({ + id: ctx.params.id, + content: body.content, + title: body.title, + metadata: body.metadata, + tags: body.tags, + }); + + ctx.body = updated; +}); + +/** + * @openapi + * /documents/search: + * post: + * tags: + * - Documents + * summary: Semantic search over documents + * description: Embeds the query text and returns the most similar documents using cosine distance. If projectId is omitted, searches across all projects the caller has access to. + * operationId: searchDocuments + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - query + * properties: + * projectId: + * type: string + * description: Project ID (optional). Omit to search across all accessible projects. + * example: 'proj_V1StGXR8Z5jdHi6B' + * query: + * type: string + * example: 'What is the capital of France?' + * limit: + * type: integer + * example: 5 + * threshold: + * type: number + * description: Minimum similarity score (0-1). Only results with score >= threshold are returned. + * example: 0.7 + * tags: + * type: object + * additionalProperties: + * type: string + * description: Filter to documents matching all these key-value tags. + * example: { env: 'production' } + * responses: + * '200': + * description: Search results + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/DocumentRecord' + * '400': + * description: Invalid request body + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +documentsRouter.post('/documents/search', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const body = ctx.request.body as { + projectId?: string; + query: string; + limit?: number; + threshold?: number; + tags?: Record; + }; - ctx.status = 200; - ctx.body = { - success: true, - document: { - id: document.id, - title: document.title, - fileId: document.fileId, - content: document.content?.toString(), - embeddingModel: document.embeddingModel, - embeddingProvider: document.embeddingProvider, - hasEmbedding: !!document.embedding, - metadata: document.metadata, - createdAt: document.createdAt, - updatedAt: document.updatedAt, - }, - }; - } catch (error) { - ctx.status = 500; - ctx.body = { - success: false, - error: error instanceof Error ? error.message : 'Unknown error', - }; + if (!body.query) { + ctx.status = 400; + ctx.body = { error: 'query is required' }; + return; } + + const projectIds = await ctx.authUser!.resolveProjectIds({ + projectPublicId: body.projectId, + action: 'documents:SearchDocuments', + }); + + if (projectIds === null) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const results = await searchDocuments({ + projectIds, + query: body.query, + limit: body.limit, + threshold: body.threshold, + tags: body.tags, + }); + + ctx.body = results; }); -documentsRouter.delete('/:id', async (ctx: Context) => { - try { - const { id } = ctx.params; - if (!id) { - ctx.status = 400; - ctx.body = { success: false, error: 'ID is required' }; - return; +/** + * @openapi + * /documents/{id}/tags: + * get: + * tags: + * - Documents + * summary: Get document tags + * operationId: getDocumentTagsRoute + * parameters: + * - name: id + * in: path + * required: true + * schema: + * type: string + * responses: + * '200': + * description: Document tags + * content: + * application/json: + * schema: + * type: object + * additionalProperties: + * type: string + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Document not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +documentsRouter.get('/documents/:id/tags', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const doc = await getDocument({ id: ctx.params.id }); + + if (!doc) { + ctx.status = 404; + ctx.body = { error: 'Document not found' }; + return; + } + + const srn = buildSrn({ + projectPublicId: doc.projectId!, + resourceType: 'document', + resourceId: doc.id, + }); + const context: Record = { 'soat:ResourceType': 'document' }; + if (doc.tags) { + for (const [k, v] of Object.entries(doc.tags)) { + context[`soat:ResourceTag/${k}`] = v; } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: doc.projectId!, + action: 'documents:GetDocument', + resource: srn, + context, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } - const deleted = await deleteDocument({ - storageConfig: defaultStorageConfig, - id, - }); + ctx.body = await getDocumentTags({ id: ctx.params.id }); +}); - if (!deleted) { - ctx.status = 404; - ctx.body = { success: false, error: 'Document not found' }; - return; +/** + * @openapi + * /documents/{id}/tags: + * put: + * tags: + * - Documents + * summary: Replace document tags + * operationId: putDocumentTags + * parameters: + * - name: id + * in: path + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * additionalProperties: + * type: string + * responses: + * '200': + * description: Tags replaced + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/DocumentRecord' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Document not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +documentsRouter.put('/documents/:id/tags', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const doc = await getDocument({ id: ctx.params.id }); + + if (!doc) { + ctx.status = 404; + ctx.body = { error: 'Document not found' }; + return; + } + + const srn = buildSrn({ + projectPublicId: doc.projectId!, + resourceType: 'document', + resourceId: doc.id, + }); + const context: Record = { 'soat:ResourceType': 'document' }; + if (doc.tags) { + for (const [k, v] of Object.entries(doc.tags)) { + context[`soat:ResourceTag/${k}`] = v; } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: doc.projectId!, + action: 'documents:UpdateDocument', + resource: srn, + context, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const tags = ctx.request.body as Record; + ctx.body = await updateDocumentTags({ + id: ctx.params.id, + tags, + merge: false, + }); +}); + +/** + * @openapi + * /documents/{id}/tags: + * patch: + * tags: + * - Documents + * summary: Merge document tags + * operationId: patchDocumentTags + * parameters: + * - name: id + * in: path + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * additionalProperties: + * type: string + * responses: + * '200': + * description: Tags merged + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/DocumentRecord' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Document not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +documentsRouter.patch('/documents/:id/tags', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const doc = await getDocument({ id: ctx.params.id }); - ctx.status = 200; - ctx.body = { success: true }; - } catch (error) { - ctx.status = 500; - ctx.body = { - success: false, - error: error instanceof Error ? error.message : 'Unknown error', - }; + if (!doc) { + ctx.status = 404; + ctx.body = { error: 'Document not found' }; + return; } + + const srn = buildSrn({ + projectPublicId: doc.projectId!, + resourceType: 'document', + resourceId: doc.id, + }); + const context: Record = { 'soat:ResourceType': 'document' }; + if (doc.tags) { + for (const [k, v] of Object.entries(doc.tags)) { + context[`soat:ResourceTag/${k}`] = v; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: doc.projectId!, + action: 'documents:UpdateDocument', + resource: srn, + context, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const tags = ctx.request.body as Record; + ctx.body = await updateDocumentTags({ id: ctx.params.id, tags, merge: true }); }); export { documentsRouter }; diff --git a/packages/server/src/rest/v1/files.ts b/packages/server/src/rest/v1/files.ts index 4c09fd8c..0316646f 100644 --- a/packages/server/src/rest/v1/files.ts +++ b/packages/server/src/rest/v1/files.ts @@ -1,111 +1,1124 @@ +import type { MulterFile } from '@ttoss/http-server'; +import { multer, Router } from '@ttoss/http-server'; +import type { Context } from 'src/Context'; +import { db } from 'src/db'; import { + createFile, deleteFile, - getFileRecord, - listFileRecords, - retrieveFileById, - saveFile, - type StorageConfig, -} from '@soat/files-core'; -import { Router } from '@ttoss/http-server'; - -import type { Context } from '../../Context'; - -const defaultConfig: StorageConfig = { - type: 'local', - local: { - path: '/tmp/files', - }, -}; - -const filesRouter = new Router(); - -filesRouter.get('/', async (ctx: Context) => { - try { - const files = await listFileRecords(); - ctx.status = 200; - ctx.body = { success: true, files }; - } catch (error) { - ctx.status = 500; - ctx.body = { - success: false, - error: error instanceof Error ? error.message : 'Unknown error', - }; + downloadFile, + getFile, + getFileTags, + listFiles, + updateFileMetadata, + updateFileTags, + uploadFile, +} from 'src/lib/files'; +import { buildSrn } from 'src/lib/iam'; + +const upload = multer({ storage: multer.memoryStorage() }); + +const filesRouter = new Router(); + +/** + * @openapi + * /files: + * get: + * tags: + * - Files + * summary: List files + * description: Returns a list of files. Requires authentication. Optionally filter by projectId. + * operationId: listFiles + * parameters: + * - name: projectId + * in: query + * required: false + * description: Filter files by project ID + * schema: + * type: string + * example: 'proj_V1StGXR8Z5jdHi6B' + * - name: limit + * in: query + * required: false + * description: Maximum number of results to return (default 50) + * schema: + * type: integer + * example: 50 + * - name: offset + * in: query + * required: false + * description: Number of results to skip (default 0) + * schema: + * type: integer + * example: 0 + * responses: + * '200': + * description: List of files returned successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/FileRecord' + * total: + * type: integer + * limit: + * type: integer + * offset: + * type: integer + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + */ +filesRouter.get('/files', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const projectPublicId = (ctx.query as Record).projectId; + const limit = ctx.query.limit + ? parseInt(ctx.query.limit as string, 10) + : undefined; + const offset = ctx.query.offset + ? parseInt(ctx.query.offset as string, 10) + : undefined; + + const projectIds = await ctx.authUser.resolveProjectIds({ + projectPublicId, + action: 'files:GetFile', + }); + + if (projectIds === null) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; } + + ctx.body = await listFiles({ + projectIds: projectIds ?? undefined, + limit, + offset, + }); }); -filesRouter.post('/upload', async (ctx: Context) => { - try { - const { content, options } = ctx.request.body; - if (!content) { - ctx.status = 400; - ctx.body = { success: false, error: 'Content is required' }; - return; +/** + * @openapi + * /files/{id}: + * get: + * tags: + * - Files + * summary: Get a file by ID + * description: Returns the data and metadata of a specific file + * operationId: getFile + * parameters: + * - name: id + * in: path + * required: true + * description: File ID + * schema: + * type: string + * example: 'abc123' + * responses: + * '200': + * description: File found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/FileRecord' + * '404': + * description: File not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '500': + * description: Internal server error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +filesRouter.get('/files/:id', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const file = await getFile({ id: ctx.params.id }); + + if (!file) { + ctx.status = 404; + ctx.body = { error: 'File not found' }; + return; + } + + // Check if user is allowed to read files in this project + const srn = buildSrn({ + projectPublicId: file.projectId!, + resourceType: 'file', + resourceId: file.id, + }); + const context: Record = { 'soat:ResourceType': 'file' }; + if (file.tags) { + for (const [k, v] of Object.entries(file.tags)) { + context[`soat:ResourceTag/${k}`] = v; } - const file = await saveFile({ config: defaultConfig, content, options }); - ctx.status = 201; - ctx.body = { - success: true, - id: file.id, - filename: options?.metadata?.filename, - }; - } catch (error) { - ctx.status = 500; - ctx.body = { - success: false, - error: error instanceof Error ? error.message : 'Unknown error', - }; } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: file.projectId!, + action: 'files:GetFile', + resource: srn, + context, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + ctx.body = file; }); -filesRouter.get('/:id', async (ctx: Context) => { - try { - const { id } = ctx.params; - if (!id) { - ctx.status = 400; - ctx.body = { success: false, error: 'ID is required' }; +/** + * @openapi + * /files: + * post: + * tags: + * - Files + * summary: Create a file + * description: Creates a new file record in the system + * operationId: createFile + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - projectId + * - storageType + * - storagePath + * properties: + * projectId: + * type: string + * example: 'proj_V1StGXR8Z5jdHi6B' + * filename: + * type: string + * example: 'document.pdf' + * contentType: + * type: string + * example: 'application/pdf' + * size: + * type: integer + * example: 1024 + * storageType: + * type: string + * enum: [local, s3, gcs] + * example: 'local' + * storagePath: + * type: string + * example: '/uploads/document.pdf' + * metadata: + * type: string + * example: '{"author":"John"}' + * responses: + * '201': + * description: File created successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/FileRecord' + * '500': + * description: Internal server error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +filesRouter.post('/files', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const body = ctx.request.body as { + projectId: string; + filename?: string; + contentType?: string; + size?: number; + storageType: 'local' | 's3' | 'gcs'; + storagePath: string; + metadata?: string; + }; + + // Check if user is allowed to create files in this project + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: body.projectId, + action: 'files:CreateFile', + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + // Convert projectId to internal ID + const project = await db.Project.findOne({ + where: { publicId: body.projectId }, + }); + if (!project) { + ctx.status = 400; + ctx.body = { error: 'Invalid project ID' }; + return; + } + + const file = await createFile({ + ...body, + projectId: project.id, + }); + ctx.status = 201; + ctx.body = file; +}); + +/** + * @openapi + * /files/{id}: + * delete: + * tags: + * - Files + * summary: Delete a file + * description: Removes a file from the system by ID + * operationId: deleteFile + * parameters: + * - name: id + * in: path + * required: true + * description: ID of the file to delete + * schema: + * type: string + * example: 'abc123' + * responses: + * '204': + * description: File deleted successfully + * '404': + * description: File not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '500': + * description: Internal server error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +filesRouter.delete('/files/:id', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + // Get file to check project permission + const file = await db.File.findOne({ + where: { publicId: ctx.params.id }, + include: [{ model: db.Project, as: 'project' }], + }); + + if (!file) { + ctx.status = 404; + ctx.body = { error: 'File not found' }; + return; + } + + // Check if user is allowed to delete files in this project + const srnDel = buildSrn({ + projectPublicId: file.project!.publicId, + resourceType: 'file', + resourceId: file.publicId, + }); + const contextDel: Record = { 'soat:ResourceType': 'file' }; + if (file.tags) { + for (const [k, v] of Object.entries(file.tags as Record)) { + contextDel[`soat:ResourceTag/${k}`] = v; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: file.project!.publicId, + action: 'files:DeleteFile', + resource: srnDel, + context: contextDel, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const result = await deleteFile({ id: ctx.params.id }); + + if (result === null) { + ctx.status = 404; + ctx.body = { error: 'File not found' }; + return; + } + + ctx.status = 204; +}); + +/** + * @openapi + * /files/upload: + * post: + * tags: + * - Files + * summary: Upload a file + * description: Uploads a file to the server and stores it in the configured storage directory + * operationId: uploadFile + * requestBody: + * required: true + * content: + * multipart/form-data: + * schema: + * type: object + * required: + * - file + * - projectId + * properties: + * file: + * type: string + * format: binary + * projectId: + * type: string + * example: 'proj_V1StGXR8Z5jdHi6B' + * metadata: + * type: string + * example: '{"author":"John"}' + * responses: + * '201': + * description: File uploaded successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/FileRecord' + * '400': + * description: Missing file or invalid project + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + */ +filesRouter.post( + '/files/upload', + upload.single('file'), + async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; return; } - const file = await retrieveFileById({ config: defaultConfig, id }); + + const body = ctx.request.body as { projectId: string; metadata?: string }; + const file = ctx.file as MulterFile | undefined; + if (!file) { - ctx.status = 404; - ctx.body = { success: false, error: 'File not found' }; + ctx.status = 400; + ctx.body = { error: 'No file provided' }; return; } - const record = await getFileRecord(id); - ctx.status = 200; - ctx.body = { success: true, file, record }; - } catch (error) { - ctx.status = 500; - ctx.body = { - success: false, - error: error instanceof Error ? error.message : 'Unknown error', - }; - } -}); -filesRouter.delete('/:id', async (ctx: Context) => { - try { - const { id } = ctx.params; - if (!id) { + if (!body.projectId) { ctx.status = 400; - ctx.body = { success: false, error: 'ID is required' }; + ctx.body = { error: 'projectId is required' }; return; } - const deleted = await deleteFile({ config: defaultConfig, id }); - if (!deleted) { - ctx.status = 404; - ctx.body = { success: false, error: 'File not found' }; + + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: body.projectId, + action: 'files:UploadFile', + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; return; } - ctx.status = 200; - ctx.body = { success: true }; - } catch (error) { - ctx.status = 500; - ctx.body = { - success: false, - error: error instanceof Error ? error.message : 'Unknown error', - }; + + const project = await db.Project.findOne({ + where: { publicId: body.projectId }, + }); + if (!project) { + ctx.status = 400; + ctx.body = { error: 'Invalid project ID' }; + return; + } + + const record = await uploadFile({ + projectId: project.id, + fileBuffer: file.buffer, + filename: file.originalname, + contentType: file.mimetype, + metadata: body.metadata, + }); + + ctx.status = 201; + ctx.body = record; + } +); + +/** + * @openapi + * /files/upload/base64: + * post: + * tags: + * - Files + * summary: Upload a file via JSON (base64-encoded) + * description: Uploads a file using a JSON body with base64-encoded content. Designed for programmatic/MCP usage. + * operationId: uploadFileBase64 + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - projectId + * - content + * properties: + * projectId: + * type: string + * example: 'proj_V1StGXR8Z5jdHi6B' + * content: + * type: string + * description: Base64-encoded file content + * filename: + * type: string + * example: 'document.txt' + * contentType: + * type: string + * example: 'text/plain' + * metadata: + * type: string + * example: '{"author":"John"}' + * responses: + * '201': + * description: File uploaded successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/FileRecord' + * '400': + * description: Missing content or invalid project + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + */ +filesRouter.post('/files/upload/base64', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const body = ctx.request.body as { + projectId: string; + content: string; + filename?: string; + contentType?: string; + metadata?: string; + }; + + if (!body.content) { + ctx.status = 400; + ctx.body = { error: 'content is required (base64-encoded)' }; + return; + } + + if (!body.projectId) { + ctx.status = 400; + ctx.body = { error: 'projectId is required' }; + return; + } + + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: body.projectId, + action: 'files:UploadFile', + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const project = await db.Project.findOne({ + where: { publicId: body.projectId }, + }); + if (!project) { + ctx.status = 400; + ctx.body = { error: 'Invalid project ID' }; + return; + } + + const fileBuffer = Buffer.from(body.content, 'base64'); + + const record = await uploadFile({ + projectId: project.id, + fileBuffer, + filename: body.filename, + contentType: body.contentType, + metadata: body.metadata, + }); + + ctx.status = 201; + ctx.body = record; +}); + +/** + * @openapi + * /files/{id}/download: + * get: + * tags: + * - Files + * summary: Download a file + * description: Streams the file content to the client + * operationId: downloadFile + * parameters: + * - name: id + * in: path + * required: true + * description: File ID + * schema: + * type: string + * example: 'fil_abc123' + * responses: + * '200': + * description: File content + * content: + * application/octet-stream: + * schema: + * type: string + * format: binary + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + * '404': + * description: File not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +filesRouter.get('/files/:id/download', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const fileRecord = await getFile({ id: ctx.params.id }); + + if (!fileRecord) { + ctx.status = 404; + ctx.body = { error: 'File not found' }; + return; + } + + const srnDl = buildSrn({ + projectPublicId: fileRecord.projectId!, + resourceType: 'file', + resourceId: fileRecord.id, + }); + const contextDl: Record = { 'soat:ResourceType': 'file' }; + if (fileRecord.tags) { + for (const [k, v] of Object.entries(fileRecord.tags)) { + contextDl[`soat:ResourceTag/${k}`] = v; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: fileRecord.projectId!, + action: 'files:DownloadFile', + resource: srnDl, + context: contextDl, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const result = await downloadFile({ id: ctx.params.id }); + + if (!result) { + ctx.status = 404; + ctx.body = { error: 'File not found on disk' }; + return; + } + + ctx.set('Content-Type', result.contentType ?? 'application/octet-stream'); + if (result.filename) { + ctx.set('Content-Disposition', `attachment; filename="${result.filename}"`); + } + if (result.size != null) { + ctx.set('Content-Length', String(result.size)); + } + ctx.body = result.stream; +}); + +/** + * @openapi + * /files/{id}/download/base64: + * get: + * tags: + * - Files + * summary: Download a file as base64 + * description: Returns JSON with base64-encoded file content. Designed for programmatic/MCP usage. + * operationId: downloadFileBase64 + * parameters: + * - name: id + * in: path + * required: true + * description: File ID + * schema: + * type: string + * example: 'fil_abc123' + * responses: + * '200': + * description: Base64-encoded file content + * content: + * application/json: + * schema: + * type: object + * properties: + * content: + * type: string + * description: Base64-encoded file content + * filename: + * type: string + * contentType: + * type: string + * size: + * type: number + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + * '404': + * description: File not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +filesRouter.get('/files/:id/download/base64', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const fileRecord = await getFile({ id: ctx.params.id }); + + if (!fileRecord) { + ctx.status = 404; + ctx.body = { error: 'File not found' }; + return; + } + + const srnDlB64 = buildSrn({ + projectPublicId: fileRecord.projectId!, + resourceType: 'file', + resourceId: fileRecord.id, + }); + const contextDlB64: Record = { 'soat:ResourceType': 'file' }; + if (fileRecord.tags) { + for (const [k, v] of Object.entries(fileRecord.tags)) { + contextDlB64[`soat:ResourceTag/${k}`] = v; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: fileRecord.projectId!, + action: 'files:DownloadFile', + resource: srnDlB64, + context: contextDlB64, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const result = await downloadFile({ id: ctx.params.id }); + + if (!result) { + ctx.status = 404; + ctx.body = { error: 'File not found on disk' }; + return; + } + + const chunks: Buffer[] = []; + for await (const chunk of result.stream) { + chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + } + const buffer = Buffer.concat(chunks); + + ctx.body = { + content: buffer.toString('base64'), + filename: result.filename, + contentType: result.contentType, + size: result.size, + }; +}); + +/** + * @openapi + * /files/{id}/metadata: + * patch: + * tags: + * - Files + * summary: Update file metadata + * description: Updates the metadata and/or filename of a file + * operationId: updateFileMetadata + * parameters: + * - name: id + * in: path + * required: true + * description: File ID + * schema: + * type: string + * example: 'fil_abc123' + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * metadata: + * type: string + * example: '{"author":"Jane","tags":["report"]}' + * filename: + * type: string + * example: 'renamed-file.txt' + * responses: + * '200': + * description: File updated successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/FileRecord' + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + * '404': + * description: File not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +filesRouter.patch('/files/:id/metadata', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const fileRecord = await getFile({ id: ctx.params.id }); + + if (!fileRecord) { + ctx.status = 404; + ctx.body = { error: 'File not found' }; + return; + } + + const srnMeta = buildSrn({ + projectPublicId: fileRecord.projectId!, + resourceType: 'file', + resourceId: fileRecord.id, + }); + const contextMeta: Record = { 'soat:ResourceType': 'file' }; + if (fileRecord.tags) { + for (const [k, v] of Object.entries(fileRecord.tags)) { + contextMeta[`soat:ResourceTag/${k}`] = v; + } } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: fileRecord.projectId!, + action: 'files:UpdateFileMetadata', + resource: srnMeta, + context: contextMeta, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const body = ctx.request.body as { metadata?: string; filename?: string }; + const updated = await updateFileMetadata({ + id: ctx.params.id, + metadata: body.metadata, + filename: body.filename, + }); + + ctx.body = updated; +}); + +/** + * @openapi + * /files/{id}/tags: + * get: + * tags: + * - Files + * summary: Get file tags + * operationId: getFileTagsRoute + * parameters: + * - name: id + * in: path + * required: true + * schema: + * type: string + * responses: + * '200': + * description: File tags + * content: + * application/json: + * schema: + * type: object + * additionalProperties: + * type: string + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + * '404': + * description: File not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +filesRouter.get('/files/:id/tags', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const file = await getFile({ id: ctx.params.id }); + + if (!file) { + ctx.status = 404; + ctx.body = { error: 'File not found' }; + return; + } + + const srn = buildSrn({ + projectPublicId: file.projectId!, + resourceType: 'file', + resourceId: file.id, + }); + const context: Record = { 'soat:ResourceType': 'file' }; + if (file.tags) { + for (const [k, v] of Object.entries(file.tags)) { + context[`soat:ResourceTag/${k}`] = v; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: file.projectId!, + action: 'files:GetFile', + resource: srn, + context, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + ctx.body = await getFileTags({ id: ctx.params.id }); +}); + +/** + * @openapi + * /files/{id}/tags: + * put: + * tags: + * - Files + * summary: Replace file tags + * operationId: putFileTags + * parameters: + * - name: id + * in: path + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * additionalProperties: + * type: string + * responses: + * '200': + * description: Tags replaced + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/FileRecord' + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + * '404': + * description: File not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +filesRouter.put('/files/:id/tags', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const file = await getFile({ id: ctx.params.id }); + + if (!file) { + ctx.status = 404; + ctx.body = { error: 'File not found' }; + return; + } + + const srn = buildSrn({ + projectPublicId: file.projectId!, + resourceType: 'file', + resourceId: file.id, + }); + const context: Record = { 'soat:ResourceType': 'file' }; + if (file.tags) { + for (const [k, v] of Object.entries(file.tags)) { + context[`soat:ResourceTag/${k}`] = v; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: file.projectId!, + action: 'files:UpdateFileMetadata', + resource: srn, + context, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const tags = ctx.request.body as Record; + ctx.body = await updateFileTags({ id: ctx.params.id, tags, merge: false }); +}); + +/** + * @openapi + * /files/{id}/tags: + * patch: + * tags: + * - Files + * summary: Merge file tags + * operationId: patchFileTags + * parameters: + * - name: id + * in: path + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * additionalProperties: + * type: string + * responses: + * '200': + * description: Tags merged + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/FileRecord' + * '401': + * $ref: '#/components/responses/Unauthorized' + * '403': + * $ref: '#/components/responses/Forbidden' + * '404': + * description: File not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +filesRouter.patch('/files/:id/tags', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const file = await getFile({ id: ctx.params.id }); + + if (!file) { + ctx.status = 404; + ctx.body = { error: 'File not found' }; + return; + } + + const srn = buildSrn({ + projectPublicId: file.projectId!, + resourceType: 'file', + resourceId: file.id, + }); + const context: Record = { 'soat:ResourceType': 'file' }; + if (file.tags) { + for (const [k, v] of Object.entries(file.tags)) { + context[`soat:ResourceTag/${k}`] = v; + } + } + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: file.projectId!, + action: 'files:UpdateFileMetadata', + resource: srn, + context, + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const tags = ctx.request.body as Record; + ctx.body = await updateFileTags({ id: ctx.params.id, tags, merge: true }); }); export { filesRouter }; diff --git a/packages/server/src/rest/v1/index.ts b/packages/server/src/rest/v1/index.ts index 3cae26c3..7e6373ec 100644 --- a/packages/server/src/rest/v1/index.ts +++ b/packages/server/src/rest/v1/index.ts @@ -1,11 +1,27 @@ import { Router } from '@ttoss/http-server'; +import { agentsRouter } from './agents'; +import { actorsRouter } from './actors'; +import { aiProvidersRouter } from './aiProviders'; +import { projectKeysRouter } from './projectKeys'; +import { conversationsRouter } from './conversations'; import { documentsRouter } from './documents'; import { filesRouter } from './files'; +import { projectsRouter } from './projects'; +import { secretsRouter } from './secrets'; +import { usersRouter } from './users'; const v1Router = new Router(); -v1Router.use('/documents', documentsRouter.routes()); -v1Router.use('/files', filesRouter.routes()); +v1Router.use(agentsRouter.routes()); +v1Router.use(actorsRouter.routes()); +v1Router.use(aiProvidersRouter.routes()); +v1Router.use(projectKeysRouter.routes()); +v1Router.use(conversationsRouter.routes()); +v1Router.use(documentsRouter.routes()); +v1Router.use(filesRouter.routes()); +v1Router.use(projectsRouter.routes()); +v1Router.use(secretsRouter.routes()); +v1Router.use(usersRouter.routes()); export { v1Router }; diff --git a/packages/server/src/rest/v1/projectKeys.ts b/packages/server/src/rest/v1/projectKeys.ts new file mode 100644 index 00000000..cfcfe9be --- /dev/null +++ b/packages/server/src/rest/v1/projectKeys.ts @@ -0,0 +1,330 @@ +import { Router } from '@ttoss/http-server'; +import type { Context } from 'src/Context'; +import { db } from 'src/db'; +import { + createProjectKey, + getProjectKey, + updateProjectKey, +} from 'src/lib/projectKeys'; + +const projectKeysRouter = new Router(); + +/** + * @openapi + * /project-keys: + * post: + * tags: + * - Project Keys + * summary: Create a new project key + * description: Creates a new project key for a user in a project with specified policy + * operationId: createProjectKey + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - projectId + * - policyId + * - name + * properties: + * projectId: + * type: string + * description: Project ID + * policyId: + * type: string + * description: Policy ID + * name: + * type: string + * description: project key name + * responses: + * '201': + * description: Project key created successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * id: + * type: string + * name: + * type: string + * key: + * type: string + * description: The full project key (shown only once) + * keyPrefix: + * type: string + * createdAt: + * type: string + * updatedAt: + * type: string + * '400': + * description: Bad request + * '403': + * description: Forbidden + * '500': + * description: Internal server error + */ +projectKeysRouter.post('/project-keys', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const { projectId, policyId, name } = ctx.request.body as { + projectId: string; + policyId: string; + name: string; + }; + + if (!projectId || !policyId || !name) { + ctx.status = 400; + ctx.body = { error: 'Missing required fields' }; + return; + } + + // Find the project + const project = await db.Project.findOne({ + where: { publicId: projectId }, + }); + + if (!project) { + ctx.status = 400; + ctx.body = { error: 'Invalid project' }; + return; + } + + // Check if user is member of the project + const membership = await db.UserProject.findOne({ + where: { + userId: ctx.authUser.id, + projectId: project.id, + }, + }); + + if (!membership) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + // Check if policy exists and belongs to the project + const policy = await db.ProjectPolicy.findOne({ + where: { + publicId: policyId, + projectId: project.id, + }, + }); + + if (!policy) { + ctx.status = 400; + ctx.body = { error: 'Invalid policy' }; + return; + } + + const projectKey = await createProjectKey({ + userId: ctx.authUser.id, + projectId: project.id, + policyId: policy.id, + name, + }); + + ctx.status = 201; + ctx.body = projectKey; +}); + +/** + * @openapi + * /project-keys/{id}: + * get: + * tags: + * - Project Keys + * summary: Get a project key by ID + * description: Returns the data and metadata of a specific project key + * operationId: getProjectKey + * parameters: + * - name: id + * in: path + * required: true + * description: Project key ID + * schema: + * type: string + * responses: + * '200': + * description: Project key found + * content: + * application/json: + * schema: + * type: object + * properties: + * id: + * type: string + * name: + * type: string + * keyPrefix: + * type: string + * userId: + * type: string + * projectId: + * type: string + * policyId: + * type: string + * createdAt: + * type: string + * updatedAt: + * type: string + * '404': + * description: Project key not found + * '403': + * description: Forbidden + * '500': + * description: Internal server error + */ +projectKeysRouter.get('/project-keys/:id', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const projectKey = await getProjectKey({ id: ctx.params.id }); + + if (!projectKey) { + ctx.status = 404; + ctx.body = { error: 'Project key not found' }; + return; + } + + // Check if user owns the project key + if (projectKey.userId !== ctx.authUser.publicId) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + ctx.body = projectKey; +}); + +/** + * @openapi + * /project-keys/{id}: + * put: + * tags: + * - Project Keys + * summary: Update a project key + * description: Updates the policy of a specific project key + * operationId: updateProjectKey + * parameters: + * - name: id + * in: path + * required: true + * description: Project key ID + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - policyId + * properties: + * policyId: + * type: string + * description: New policy ID + * responses: + * '200': + * description: Project key updated successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * id: + * type: string + * name: + * type: string + * keyPrefix: + * type: string + * userId: + * type: string + * projectId: + * type: string + * policyId: + * type: string + * createdAt: + * type: string + * updatedAt: + * type: string + * '404': + * description: Project key not found + * '403': + * description: Forbidden + * '500': + * description: Internal server error + */ +projectKeysRouter.put('/project-keys/:id', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const { policyId } = ctx.request.body as { + policyId: string; + }; + + if (!policyId) { + ctx.status = 400; + ctx.body = { error: 'Missing policyId' }; + return; + } + + // Find the policy + const policy = await db.ProjectPolicy.findOne({ + where: { publicId: policyId }, + }); + + if (!policy) { + ctx.status = 400; + ctx.body = { error: 'Invalid policy' }; + return; + } + + // Check if user owns the project key + const projectKeyRecord = await ctx.db.ProjectKey.findOne({ + where: { publicId: ctx.params.id }, + include: [{ model: db.User }, { model: db.Project }], + }); + + if (!projectKeyRecord) { + ctx.status = 404; + ctx.body = { error: 'Project key not found' }; + return; + } + + if (projectKeyRecord.userId !== ctx.authUser.id) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + // Check if policy belongs to the same project as the project key + if (policy.projectId !== projectKeyRecord.projectId) { + ctx.status = 400; + ctx.body = { error: 'Policy does not belong to the same project' }; + return; + } + + const updatedProjectKey = await updateProjectKey({ + id: ctx.params.id, + policyId: policy.id, + }); + + ctx.body = updatedProjectKey; +}); + +export { projectKeysRouter }; diff --git a/packages/server/src/rest/v1/projects.ts b/packages/server/src/rest/v1/projects.ts new file mode 100644 index 00000000..53e444b8 --- /dev/null +++ b/packages/server/src/rest/v1/projects.ts @@ -0,0 +1,952 @@ +import { Router } from '@ttoss/http-server'; +import type { Context } from 'src/Context'; +import { + addUserToProject, + createProject, + createProjectPolicy, + deleteProject, + deleteProjectPolicy, + getProject, + getProjectPolicy, + getUserProjectPolicies, + listProjectPolicies, + listProjects, + updateProjectPolicy, + updateUserProjectPolicies, +} from 'src/lib/projects'; + +const projectsRouter = new Router(); + +/** + * @openapi + * /projects: + * post: + * tags: + * - Projects + * summary: Create a project + * description: Creates a new project. Only admins can create projects. + * operationId: createProject + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - name + * properties: + * name: + * type: string + * example: 'My Project' + * responses: + * '201': + * description: Project created successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ProjectRecord' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +projectsRouter.post('/projects', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + if (ctx.authUser.role !== 'admin') { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const { name } = ctx.request.body as { name: string }; + + const project = await createProject({ name }); + + ctx.status = 201; + ctx.body = project; +}); + +/** + * @openapi + * /projects/{projectId}/policies: + * get: + * tags: + * - Projects + * summary: List project policies + * description: Returns a list of policies for a project. Project members can list policies. + * operationId: listProjectPolicies + * parameters: + * - name: projectId + * in: path + * required: true + * description: Project ID + * schema: + * type: string + * example: 'proj_V1StGXR8Z5jdHi6B' + * responses: + * '200': + * description: List of policies returned successfully + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/ProjectPolicyRecord' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Project not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +projectsRouter.get('/projects/:projectId/policies', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + // Check if user is member of the project + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: ctx.params.projectId, + action: 'projects:GetProject', + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const policies = await listProjectPolicies({ + projectId: ctx.params.projectId, + }); + + ctx.body = policies; +}); + +/** + * @openapi + * /projects/{projectId}/policies: + * post: + * tags: + * - Projects + * summary: Create a project policy + * description: Creates a new policy for a project. Only admins can create policies. + * operationId: createProjectPolicy + * parameters: + * - name: projectId + * in: path + * required: true + * description: Project ID + * schema: + * type: string + * example: 'proj_V1StGXR8Z5jdHi6B' + * requestBody: + * required: true + * content:document + * properties: + * name: + * type: string + * example: 'Document Readers' + * description: + * type: string + * example: 'Read-only access to documents' + * document: + * type: object + * description: PolicyDocument JSON + * responses: + * '201': + * description: Policy created successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ProjectPolicyRecord' + * '400': + * description: Invalid policy document + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Project not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +projectsRouter.post('/projects/:projectId/policies', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + if (ctx.authUser.role !== 'admin') { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const { name, description, permissions, notPermissions } = ctx.request + .body as { + name?: string; + description?: string; + permissions: string[]; + notPermissions?: string[]; + }; + + const document: import('src/lib/iam').PolicyDocument = { + statement: [ + ...(permissions?.length + ? [{ effect: 'Allow' as const, action: permissions }] + : []), + ...(notPermissions?.length + ? [{ effect: 'Deny' as const, action: notPermissions }] + : []), + ], + }; + + const result = await createProjectPolicy({ + projectId: ctx.params.projectId, + name, + description, + document, + }); + + if (result === 'not_found') { + ctx.status = 404; + ctx.body = { error: 'Project not found' }; + return; + } + + if ('invalid' in result) { + ctx.status = 400; + ctx.body = { error: 'Invalid policy document', details: result.errors }; + return; + } + + ctx.status = 201; + ctx.body = result; +}); + +/** + * @openapi + * /projects/{projectId}/policies/{policyId}: + * put: + * tags: + * - Projects + * summary: Update a project policy + * description: Replaces a policy document. Only admins can update policies. + * operationId: updateProjectPolicy + * parameters: + * - name: projectId + * in: path + * required: true + * description: Project ID + * schema: + * type: string + * - name: policyId + * in: path + * required: true + * description: Policy ID + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - document + * properties: + * name: + * type: string + * description: + * type: string + * document: + * type: object + * responses: + * '200': + * description: Policy updated successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ProjectPolicyRecord' + * '400': + * description: Invalid policy document + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Project or policy not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +projectsRouter.put( + '/projects/:projectId/policies/:policyId', + async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + if (ctx.authUser.role !== 'admin') { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const { name, description, document } = ctx.request.body as { + name?: string; + description?: string; + document: object; + }; + + const result = await updateProjectPolicy({ + projectId: ctx.params.projectId, + policyId: ctx.params.policyId, + name, + description, + document: document as import('src/lib/iam').PolicyDocument, + }); + + if (result === 'not_found') { + ctx.status = 404; + ctx.body = { error: 'Project or policy not found' }; + return; + } + + if ('invalid' in result) { + ctx.status = 400; + ctx.body = { error: 'Invalid policy document', details: result.errors }; + return; + } + + ctx.body = result; + } +); + +/** + * @openapi + * /projects/{projectId}/policies/{policyId}: + * delete: + * tags: + * - Projects + * summary: Delete a project policy + * description: Deletes a policy. Only admins can delete policies. + * operationId: deleteProjectPolicy + * parameters: + * - name: projectId + * in: path + * required: true + * description: Project ID + * schema: + * type: string + * - name: policyId + * in: path + * required: true + * description: Policy ID + * schema: + * type: string + * responses: + * '204': + * description: Policy deleted successfully + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Project or policy not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +projectsRouter.delete( + '/projects/:projectId/policies/:policyId', + async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + if (ctx.authUser.role !== 'admin') { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const result = await deleteProjectPolicy({ + projectId: ctx.params.projectId, + policyId: ctx.params.policyId, + }); + + if (result === 'not_found') { + ctx.status = 404; + ctx.body = { error: 'Project or policy not found' }; + return; + } + + ctx.status = 204; + } +); + +/** + * @openapi + * /projects/{projectId}/policies/{policyId}: + * get: + * tags: + * - Projects + * summary: Get a project policy + * description: Returns a single policy for a project. + * operationId: getProjectPolicy + * parameters: + * - name: projectId + * in: path + * required: true + * description: Project ID + * schema: + * type: string + * - name: policyId + * in: path + * required: true + * description: Policy ID + * schema: + * type: string + * responses: + * '200': + * description: Policy returned successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ProjectPolicyRecord' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Policy not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +projectsRouter.get( + '/projects/:projectId/policies/:policyId', + async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: ctx.params.projectId, + action: 'projects:GetProject', + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const policy = await getProjectPolicy({ + projectId: ctx.params.projectId, + policyId: ctx.params.policyId, + }); + + if (!policy) { + ctx.status = 404; + ctx.body = { error: 'Policy not found' }; + return; + } + + ctx.body = policy; + } +); + +/** + * @openapi + * /projects/{projectId}/policies/{policyId}: + * put: + * tags: + * - Projects + * summary: Update a project policy + * properties: + * userId: + * type: string + * example: 'usr_V1StGXR8Z5jdHi6B' + * policyIds: + * type: array + * items: + * type: string + * example: ['pol_V1StGXR8Z5jdHi6B'] + * responses: + * '201': + * description: User added to project successfully + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Project, user, or policy not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +projectsRouter.post('/projects/:projectId/members', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + if (ctx.authUser.role !== 'admin') { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const { userId, policyId, policyIds } = ctx.request.body as { + userId: string; + policyId?: string; + policyIds?: string[]; + }; + + const resolvedPolicyIds = policyIds ?? (policyId ? [policyId] : undefined); + + const success = await addUserToProject({ + projectId: ctx.params.projectId, + userId, + policyIds: resolvedPolicyIds, + }); + + if (!success) { + ctx.status = 404; + ctx.body = { error: 'Project, user, or policy not found' }; + return; + } + + ctx.status = 201; +}); + +/** + * @openapi + * /projects/{projectId}/members/{userId}/policies: + * put: + * tags: + * - Projects + * summary: Update member policies + * description: Replaces the list of policies attached to a member. Only admins can update member policies. + * operationId: updateUserProjectPolicies + * parameters: + * - name: projectId + * in: path + * required: true + * description: Project ID + * schema: + * type: string + * - name: userId + * in: path + * required: true + * description: User ID + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - policyIds + * properties: + * policyIds: + * type: array + * items: + * type: string + * responses: + * '204': + * description: Member policies updated successfully + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Project, user, or policy not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +projectsRouter.put( + '/projects/:projectId/members/:userId/policies', + async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + if (ctx.authUser.role !== 'admin') { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const { policyIds } = ctx.request.body as { policyIds: string[] }; + + const result = await updateUserProjectPolicies({ + projectId: ctx.params.projectId, + userId: ctx.params.userId, + policyIds, + }); + + if (result === 'not_found') { + ctx.status = 404; + ctx.body = { error: 'Project, user, membership, or policy not found' }; + return; + } + + ctx.status = 204; + } +); + +/** + * @openapi + * /projects/{projectId}/members/{userId}/policies: + * get: + * tags: + * - Projects + * summary: Get member policies + * description: Returns the list of policies attached to a project member. + * operationId: getUserProjectPolicies + * parameters: + * - name: projectId + * in: path + * required: true + * description: Project ID + * schema: + * type: string + * - name: userId + * in: path + * required: true + * description: User ID + * schema: + * type: string + * responses: + * '200': + * description: Member policies returned successfully + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/ProjectPolicyRecord' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Project or user not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +projectsRouter.get( + '/projects/:projectId/members/:userId/policies', + async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + if (ctx.authUser.role !== 'admin') { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const policies = await getUserProjectPolicies({ + projectId: ctx.params.projectId, + userId: ctx.params.userId, + }); + + if (policies === null) { + ctx.status = 404; + ctx.body = { error: 'Project or user not found' }; + return; + } + + ctx.body = policies; + } +); + +/** + * @openapi + * /projects: + * get: + * tags: + * - Projects + * summary: List projects + * description: Admins see all projects. Members see only their own projects. + * operationId: listProjects + * responses: + * '200': + * description: List of projects returned successfully + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/ProjectRecord' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +projectsRouter.get('/projects', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const projects = await listProjects({ authUser: ctx.authUser }); + ctx.body = projects; +}); + +/** + * @openapi + * /projects/{id}: + * get: + * tags: + * - Projects + * summary: Get a project + * description: Admins can get any project. Members can only get projects they belong to. + * operationId: getProject + * parameters: + * - name: id + * in: path + * required: true + * description: Project ID + * schema: + * type: string + * example: 'proj_V1StGXR8Z5jdHi6B' + * responses: + * '200': + * description: Project returned successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ProjectRecord' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Project not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +projectsRouter.get('/projects/:id', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const result = await getProject({ + id: ctx.params.id, + authUser: ctx.authUser, + }); + + if (result === 'not_found') { + ctx.status = 404; + ctx.body = { error: 'Project not found' }; + return; + } + + if (result === 'forbidden') { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + ctx.body = result; +}); + +/** + * @openapi + * /projects/{id}: + * delete: + * tags: + * - Projects + * summary: Delete a project + * description: Only admins can delete projects. + * operationId: deleteProject + * parameters: + * - name: id + * in: path + * required: true + * description: Project ID + * schema: + * type: string + * example: 'proj_V1StGXR8Z5jdHi6B' + * responses: + * '204': + * description: Project deleted successfully + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Project not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +projectsRouter.delete('/projects/:id', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + if (ctx.authUser.role !== 'admin') { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const result = await deleteProject({ id: ctx.params.id }); + + if (!result) { + ctx.status = 404; + ctx.body = { error: 'Project not found' }; + return; + } + + ctx.status = 204; +}); + +export { projectsRouter }; diff --git a/packages/server/src/rest/v1/secrets.ts b/packages/server/src/rest/v1/secrets.ts new file mode 100644 index 00000000..d963ff20 --- /dev/null +++ b/packages/server/src/rest/v1/secrets.ts @@ -0,0 +1,441 @@ +import { Router } from '@ttoss/http-server'; +import type { Context } from 'src/Context'; +import { db } from 'src/db'; +import { + createSecret, + deleteSecret, + getSecret, + listSecrets, + updateSecret, +} from 'src/lib/secrets'; + +const secretsRouter = new Router(); + +/** + * @openapi + * /secrets: + * get: + * tags: + * - Secrets + * summary: List secrets + * description: Returns all secrets in the project. Values are never returned. + * operationId: listSecrets + * parameters: + * - name: projectId + * in: query + * required: false + * description: Project ID + * schema: + * type: string + * example: 'proj_V1StGXR8Z5jdHi6B' + * responses: + * '200': + * description: List of secrets + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/SecretRecord' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +secretsRouter.get('/secrets', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const projectPublicId = ctx.query.projectId as string | undefined; + + const projectIds = await ctx.authUser.resolveProjectIds({ + projectPublicId, + action: 'secrets:ListSecrets', + }); + + if (projectIds === null) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + ctx.body = await listSecrets({ projectIds: projectIds ?? [] }); +}); + +/** + * @openapi + * /secrets/{secretId}: + * get: + * tags: + * - Secrets + * summary: Get a secret by ID + * description: Returns secret metadata. The value is never returned. + * operationId: getSecret + * parameters: + * - name: secretId + * in: path + * required: true + * description: Secret ID + * schema: + * type: string + * example: 'sec_V1StGXR8Z5jdHi6B' + * responses: + * '200': + * description: Secret found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/SecretRecord' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Secret not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +secretsRouter.get('/secrets/:secretId', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const secret = await getSecret({ id: ctx.params.secretId }); + + if (!secret) { + ctx.status = 404; + ctx.body = { error: 'Secret not found' }; + return; + } + + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: secret.projectId!, + action: 'secrets:GetSecret', + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + ctx.body = secret; +}); + +/** + * @openapi + * /secrets: + * post: + * tags: + * - Secrets + * summary: Create a secret + * description: Creates a new secret. The value is encrypted at rest and never returned. + * operationId: createSecret + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - name + * properties: + * projectId: + * type: string + * description: Project ID. Required for JWT auth; omit when using a project key. + * example: 'proj_V1StGXR8Z5jdHi6B' + * name: + * type: string + * example: 'OpenAI Production Key' + * value: + * type: string + * description: The secret value to encrypt and store + * example: 'sk-...' + * responses: + * '201': + * description: Secret created + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/SecretRecord' + * '400': + * description: Invalid request body + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +secretsRouter.post('/secrets', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const body = ctx.request.body as { + projectId?: string; + name?: string; + value?: string; + }; + + if (!body.name) { + ctx.status = 400; + ctx.body = { error: 'name is required' }; + return; + } + + let resolvedProjectPublicId = body.projectId; + if (!resolvedProjectPublicId) { + if (ctx.authUser.projectKeyProjectId) { + resolvedProjectPublicId = ctx.authUser.projectKeyProjectId; + } else { + ctx.status = 400; + ctx.body = { error: 'projectId is required' }; + return; + } + } + + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: resolvedProjectPublicId, + action: 'secrets:CreateSecret', + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const project = await db.Project.findOne({ + where: { publicId: resolvedProjectPublicId }, + }); + if (!project) { + ctx.status = 400; + ctx.body = { error: 'Invalid project ID' }; + return; + } + + const secret = await createSecret({ + projectId: project.id, + name: body.name, + value: body.value, + }); + + ctx.status = 201; + ctx.body = secret; +}); + +/** + * @openapi + * /secrets/{secretId}: + * patch: + * tags: + * - Secrets + * summary: Update a secret + * description: Updates the name or value of a secret. + * operationId: updateSecret + * parameters: + * - name: secretId + * in: path + * required: true + * description: Secret ID + * schema: + * type: string + * example: 'sec_V1StGXR8Z5jdHi6B' + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * name: + * type: string + * value: + * type: string + * description: New secret value to encrypt and store + * responses: + * '200': + * description: Secret updated + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/SecretRecord' + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Secret not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +secretsRouter.patch('/secrets/:secretId', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const secret = await getSecret({ id: ctx.params.secretId }); + if (!secret) { + ctx.status = 404; + ctx.body = { error: 'Secret not found' }; + return; + } + + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: secret.projectId!, + action: 'secrets:UpdateSecret', + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const body = ctx.request.body as { name?: string; value?: string }; + + const updated = await updateSecret({ + id: ctx.params.secretId, + name: body.name, + value: body.value, + }); + + ctx.body = updated; +}); + +/** + * @openapi + * /secrets/{secretId}: + * delete: + * tags: + * - Secrets + * summary: Delete a secret + * description: Deletes a secret. Returns 409 if referenced by an AI provider unless force=true. + * operationId: deleteSecret + * parameters: + * - name: secretId + * in: path + * required: true + * description: Secret ID + * schema: + * type: string + * example: 'sec_V1StGXR8Z5jdHi6B' + * - name: force + * in: query + * required: false + * description: If true, also delete dependent AI providers + * schema: + * type: boolean + * responses: + * '204': + * description: Secret deleted + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: Secret not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '409': + * description: Conflict — secret is referenced by one or more AI providers + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +secretsRouter.delete('/secrets/:secretId', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + const secret = await getSecret({ id: ctx.params.secretId }); + if (!secret) { + ctx.status = 404; + ctx.body = { error: 'Secret not found' }; + return; + } + + const allowed = await ctx.authUser.isAllowed({ + projectPublicId: secret.projectId!, + action: 'secrets:DeleteSecret', + }); + if (!allowed) { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const force = ctx.query.force === 'true'; + const result = await deleteSecret({ id: ctx.params.secretId, force }); + + if (result === 'conflict') { + ctx.status = 409; + ctx.body = { + error: + 'Secret is referenced by one or more AI providers. Use force=true to delete them as well.', + }; + return; + } + + ctx.status = 204; +}); + +export { secretsRouter }; diff --git a/packages/server/src/rest/v1/users.ts b/packages/server/src/rest/v1/users.ts new file mode 100644 index 00000000..6bf8c3ce --- /dev/null +++ b/packages/server/src/rest/v1/users.ts @@ -0,0 +1,375 @@ +import { Router } from '@ttoss/http-server'; +import type { Context } from 'src/Context'; +import { + createFirstAdminUser, + createUser, + deleteUser, + getUser, + listUsers, + loginUser, +} from 'src/lib/users'; + +const usersRouter = new Router(); + +/** + * @openapi + * /users: + * get: + * tags: + * - Users + * summary: List all users + * description: Returns a list of all users + * operationId: listUsers + * responses: + * '200': + * description: List of users returned successfully + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/UserRecord' + * '500': + * description: Internal server error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +usersRouter.get('/users', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + if (ctx.authUser.role !== 'admin') { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + ctx.body = await listUsers(); +}); + +/** + * @openapi + * /users/{id}: + * get: + * tags: + * - Users + * summary: Get a user by ID + * description: Returns the data of a specific user + * operationId: getUser + * parameters: + * - name: id + * in: path + * required: true + * description: User ID + * schema: + * type: string + * example: 'usr_V1StGXR8Z5jdHi6B' + * responses: + * '200': + * description: User found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/UserRecord' + * '404': + * description: User not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '500': + * description: Internal server error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +usersRouter.get('/users/:id', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + if (ctx.authUser.role !== 'admin') { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const user = await getUser({ id: ctx.params.id }); + + if (!user) { + ctx.status = 404; + ctx.body = { error: 'User not found' }; + return; + } + + ctx.body = user; +}); + +/** + * @openapi + * /users: + * post: + * tags: + * - Users + * summary: Create a user + * description: Creates a new user in the system + * operationId: createUser + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - username + * - password + * properties: + * username: + * type: string + * example: 'johndoe' + * password: + * type: string + * format: password + * example: 'supersecret' + * role: + * type: string + * enum: [admin, user] + * example: 'user' + * responses: + * '201': + * description: User created successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/UserRecord' + * '500': + * description: Internal server error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +usersRouter.post('/users', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + if (ctx.authUser.role !== 'admin') { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const body = ctx.request.body as { + username: string; + password: string; + role?: 'admin' | 'user'; + }; + + const user = await createUser(body); + ctx.status = 201; + ctx.body = user; +}); + +/** + * @openapi + * /users/bootstrap: + * post: + * tags: + * - Users + * summary: Create the first admin user + * description: Creates the first admin user. Returns 409 if any user already exists. + * operationId: bootstrapUser + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - username + * - password + * properties: + * username: + * type: string + * example: 'admin' + * password: + * type: string + * format: password + * example: 'supersecret' + * responses: + * '201': + * description: Admin user created successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/UserRecord' + * '409': + * description: Users already exist + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '500': + * description: Internal server error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +usersRouter.post('/users/bootstrap', async (ctx: Context) => { + const body = ctx.request.body as { + username: string; + password: string; + }; + + const user = await createFirstAdminUser(body); + + if (!user) { + ctx.status = 409; + ctx.body = { error: 'Users already exist' }; + return; + } + + ctx.status = 201; + ctx.body = user; +}); + +/** + * @openapi + * /users/login: + * post: + * tags: + * - Users + * summary: Login + * description: Authenticates a user and returns a JWT token + * operationId: loginUser + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - username + * - password + * properties: + * username: + * type: string + * example: 'admin' + * password: + * type: string + * format: password + * example: 'supersecret' + * responses: + * '200': + * description: Login successful + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/UserRecord' + * - type: object + * required: + * - token + * properties: + * token: + * type: string + * '401': + * description: Invalid credentials + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +usersRouter.post('/users/login', async (ctx: Context) => { + const { username, password } = ctx.request.body as { + username: string; + password: string; + }; + + const result = await loginUser({ username, password }); + + if (!result) { + ctx.status = 401; + ctx.body = { error: 'Invalid credentials' }; + return; + } + + ctx.body = result; +}); + +/** + * @openapi + * /users/{id}: + * delete: + * tags: + * - Users + * summary: Delete a user + * description: Deletes a user by ID. Admin only. + * operationId: deleteUser + * parameters: + * - name: id + * in: path + * required: true + * description: User ID + * schema: + * type: string + * example: 'usr_V1StGXR8Z5jdHi6B' + * responses: + * '204': + * description: User deleted successfully + * '401': + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '403': + * description: Forbidden + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * '404': + * description: User not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +usersRouter.delete('/users/:id', async (ctx: Context) => { + if (!ctx.authUser) { + ctx.status = 401; + ctx.body = { error: 'Unauthorized' }; + return; + } + + if (ctx.authUser.role !== 'admin') { + ctx.status = 403; + ctx.body = { error: 'Forbidden' }; + return; + } + + const deleted = await deleteUser({ id: ctx.params.id }); + + if (!deleted) { + ctx.status = 404; + ctx.body = { error: 'User not found' }; + return; + } + + ctx.status = 204; +}); + +export { usersRouter }; diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index adf8c621..d4f7bdbb 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -1,10 +1,7 @@ -/* eslint-disable turbo/no-undeclared-env-vars */ import 'dotenv/config'; -import { models } from '@soat/postgresdb'; -import { initialize } from '@ttoss/postgresdb'; - import { app } from './app'; +import { initializeDatabase } from './db'; /** * SOAT = 5047 @@ -13,14 +10,8 @@ const SOAT_PORT = process.env.PORT || 5047; const startServer = async () => { try { - await initialize({ - models, - host: process.env.DATABASE_HOST, - port: Number(process.env.DATABASE_PORT), - database: process.env.DATABASE_NAME, - username: process.env.DATABASE_USER, - password: process.env.DATABASE_PASSWORD, - }); + const database = await initializeDatabase(app); + await database.sequelize.sync({ alter: true }); // Sync models with the database } catch (error) { // eslint-disable-next-line no-console console.error('Failed to connect to database:', error); diff --git a/packages/server/tests/tsconfig.json b/packages/server/tests/tsconfig.json index 397c60cd..e48943c2 100644 --- a/packages/server/tests/tsconfig.json +++ b/packages/server/tests/tsconfig.json @@ -2,6 +2,7 @@ "extends": "@ttoss/config/tsconfig.test.json", "compilerOptions": { "paths": { + "dist": ["../dist/"], "src/*": ["../src/*"], "tests/*": ["./*"] } diff --git a/packages/server/tests/unit/jest.config.ts b/packages/server/tests/unit/jest.config.ts index 6ba531c0..d6687461 100644 --- a/packages/server/tests/unit/jest.config.ts +++ b/packages/server/tests/unit/jest.config.ts @@ -1,8 +1,14 @@ import { jestUnitConfig } from '@ttoss/config'; +import { getTransformIgnorePatterns } from '@ttoss/test-utils'; export default jestUnitConfig({ coverageThreshold: { global: {}, }, - setupFiles: ['/setupTests.ts'], + maxWorkers: 2, + // setupFiles: ['/setupTests.ts'], + setupFilesAfterEnv: ['/setupTestsAfterEnv.ts'], + transformIgnorePatterns: getTransformIgnorePatterns({ + esmModules: ['@ttoss/postgresdb', '@ttoss/http-server-mcp', 'nanoid'], + }), }); diff --git a/packages/server/tests/unit/setupTests.ts b/packages/server/tests/unit/setupTests.ts deleted file mode 100644 index 3db7c7d9..00000000 --- a/packages/server/tests/unit/setupTests.ts +++ /dev/null @@ -1,26 +0,0 @@ -jest.mock('@soat/postgresdb'); - -jest.mock('@soat/documents-core', () => { - return { - createDocument: jest.fn(), - getDocument: jest.fn(), - updateDocument: jest.fn(), - deleteDocument: jest.fn(), - listDocuments: jest.fn(), - searchDocumentsBySimilarity: jest.fn(), - }; -}); -jest.mock('@soat/embeddings-core', () => { - return { - getConfigFromEnv: jest.fn(), - }; -}); -jest.mock('@soat/files-core', () => { - return { - saveFile: jest.fn(), - deleteFile: jest.fn(), - retrieveFileById: jest.fn(), - listFileRecords: jest.fn(), - getFileRecord: jest.fn(), - }; -}); diff --git a/packages/server/tests/unit/setupTestsAfterEnv.ts b/packages/server/tests/unit/setupTestsAfterEnv.ts new file mode 100644 index 00000000..1606a87b --- /dev/null +++ b/packages/server/tests/unit/setupTestsAfterEnv.ts @@ -0,0 +1,62 @@ +jest.mock('ollama', () => { + return { + Ollama: jest.fn().mockImplementation(() => { + return { + embed: jest.fn().mockResolvedValue({ + embeddings: [Array(1024).fill(0.1)], + }), + chat: jest.fn().mockResolvedValue( + (async function* () { + yield { message: { content: 'mock', role: 'assistant' } }; + })() + ), + }; + }), + }; +}); + +import { models } from '@soat/postgresdb'; +import type { StartedPostgreSqlContainer } from '@testcontainers/postgresql'; +import { PostgreSqlContainer } from '@testcontainers/postgresql'; +import type { Sequelize } from '@ttoss/postgresdb'; +import { initialize } from '@ttoss/postgresdb'; +import { app } from 'src/app'; +import { initializeDatabase } from 'src/db'; + +let sequelize: Sequelize; +let postgresContainer: StartedPostgreSqlContainer; + +jest.setTimeout(120000); + +beforeAll(async () => { + postgresContainer = await new PostgreSqlContainer( + 'pgvector/pgvector:0.8.1-pg18-trixie' + ).start(); + + try { + const db = await initialize({ + models, + logging: false, + username: postgresContainer.getUsername(), + password: postgresContainer.getPassword(), + database: postgresContainer.getDatabase(), + host: postgresContainer.getHost(), + port: postgresContainer.getPort(), + }); + + await initializeDatabase(app); + + sequelize = db.sequelize; + + await sequelize.sync(); + } catch (error) { + // eslint-disable-next-line no-console + console.error('Error during database initialization:', error); + throw error; + } +}); + +afterAll(async () => { + await sequelize?.close(); + await postgresContainer?.stop(); +}); diff --git a/packages/server/tests/unit/testClient.ts b/packages/server/tests/unit/testClient.ts new file mode 100644 index 00000000..63527adc --- /dev/null +++ b/packages/server/tests/unit/testClient.ts @@ -0,0 +1,44 @@ +import { app } from 'src/app'; +import request from 'supertest'; + +export const testClient = request(app.callback()); + +/** + * Logs in with the given credentials and returns a Bearer token. + * Drives the implementation of POST /api/v1/users/login. + */ +export const loginAs = async ( + username: string, + password: string +): Promise => { + const res = await testClient + .post('/api/v1/users/login') + .send({ username, password }); + return res.body.token as string; +}; + +/** + * Helper to create authenticated requests with proper Origin header + * Use this for requests that require JWT authentication + */ +export const authenticatedTestClient = (token: string) => { + const Authorization = `Bearer ${token}`; + + return { + get: (url: string) => { + return testClient.get(url).set('Authorization', Authorization); + }, + post: (url: string) => { + return testClient.post(url).set('Authorization', Authorization); + }, + put: (url: string) => { + return testClient.put(url).set('Authorization', Authorization); + }, + patch: (url: string) => { + return testClient.patch(url).set('Authorization', Authorization); + }, + delete: (url: string) => { + return testClient.delete(url).set('Authorization', Authorization); + }, + }; +}; diff --git a/packages/server/tests/unit/tests/actors.test.ts b/packages/server/tests/unit/tests/actors.test.ts new file mode 100644 index 00000000..c6a61a92 --- /dev/null +++ b/packages/server/tests/unit/tests/actors.test.ts @@ -0,0 +1,407 @@ +import { authenticatedTestClient, loginAs, testClient } from '../testClient'; + +describe('Actors', () => { + let adminToken: string; + let userToken: string; + let userId: string; + let projectId: string; + let policyId: string; + + beforeAll(async () => { + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + adminToken = await loginAs('admin', 'supersecret'); + + const createUserRes = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'actorsuser', password: 'actorspass' }); + + userId = createUserRes.body.id; + userToken = await loginAs('actorsuser', 'actorspass'); + + const projectRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Actors Test Project' }); + projectId = projectRes.body.id; + + const policyRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: [ + 'actors:ListActors', + 'actors:GetActor', + 'actors:CreateActor', + 'actors:DeleteActor', + 'actors:UpdateActor', + ], + }); + policyId = policyRes.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId, policyId }); + }); + + describe('POST /api/v1/actors', () => { + test('authenticated user with permission can create an actor', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/actors') + .send({ projectId, name: 'Alice' }); + + expect(response.status).toBe(201); + expect(response.body.id).toMatch(/^act_/); + expect(response.body.name).toBe('Alice'); + expect(response.body.projectId).toBe(projectId); + expect(response.body.type).toBeUndefined(); + expect(response.body.externalId).toBeUndefined(); + }); + + test('can create an actor with type and externalId', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/actors') + .send({ + projectId, + name: 'Bob', + type: 'customer', + externalId: '+15550001111', + }); + + expect(response.status).toBe(201); + expect(response.body.name).toBe('Bob'); + expect(response.body.type).toBe('customer'); + expect(response.body.externalId).toBe('+15550001111'); + }); + + test('duplicate externalId within same project returns 409', async () => { + await authenticatedTestClient(userToken) + .post('/api/v1/actors') + .send({ projectId, name: 'Charlie', externalId: '+15559999999' }); + + const response = await authenticatedTestClient(userToken) + .post('/api/v1/actors') + .send({ projectId, name: 'Charlie2', externalId: '+15559999999' }); + + expect(response.status).toBe(409); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient + .post('/api/v1/actors') + .send({ projectId, name: 'Anon' }); + + expect(response.status).toBe(401); + }); + + test('missing name returns 400', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/actors') + .send({ projectId }); + + expect(response.status).toBe(400); + }); + + test('missing projectId returns 400 for JWT users', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/actors') + .send({ name: 'NoProject' }); + + expect(response.status).toBe(400); + }); + }); + + describe('GET /api/v1/actors', () => { + beforeAll(async () => { + await authenticatedTestClient(userToken) + .post('/api/v1/actors') + .send({ projectId, name: 'ListActor1' }); + await authenticatedTestClient(userToken) + .post('/api/v1/actors') + .send({ projectId, name: 'ListActor2' }); + }); + + test('authenticated user with permission can list actors', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/actors?projectId=${projectId}` + ); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body.data)).toBe(true); + expect(response.body.data.length).toBeGreaterThanOrEqual(2); + expect(response.body.total).toBeGreaterThanOrEqual(2); + }); + + test('listing without projectId returns all accessible actors', async () => { + const response = + await authenticatedTestClient(userToken).get('/api/v1/actors'); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body.data)).toBe(true); + }); + + test('can filter by externalId', async () => { + await authenticatedTestClient(userToken).post('/api/v1/actors').send({ + projectId, + name: 'ExternalFiltered', + externalId: '+15558887777', + }); + + const response = await authenticatedTestClient(userToken).get( + `/api/v1/actors?externalId=%2B15558887777` + ); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body.data)).toBe(true); + expect( + response.body.data.some((a: { externalId: string }) => { + return a.externalId === '+15558887777'; + }) + ).toBe(true); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient.get( + `/api/v1/actors?projectId=${projectId}` + ); + + expect(response.status).toBe(401); + }); + }); + + describe('GET /api/v1/actors/:id', () => { + let actorId: string; + + beforeAll(async () => { + const res = await authenticatedTestClient(userToken) + .post('/api/v1/actors') + .send({ projectId, name: 'FetchActor', type: 'agent' }); + actorId = res.body.id; + }); + + test('user with permission can get an actor by ID', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/actors/${actorId}` + ); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(actorId); + expect(response.body.name).toBe('FetchActor'); + expect(response.body.type).toBe('agent'); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient.get(`/api/v1/actors/${actorId}`); + + expect(response.status).toBe(401); + }); + + test('returns 404 for non-existent actor', async () => { + const response = await authenticatedTestClient(adminToken).get( + '/api/v1/actors/act_nonexistent' + ); + + expect(response.status).toBe(404); + }); + }); + + describe('DELETE /api/v1/actors/:id', () => { + test('user with permission can delete an actor', async () => { + const createRes = await authenticatedTestClient(userToken) + .post('/api/v1/actors') + .send({ projectId, name: 'ToDelete' }); + const actorId = createRes.body.id; + + const deleteRes = await authenticatedTestClient(userToken).delete( + `/api/v1/actors/${actorId}` + ); + + expect(deleteRes.status).toBe(204); + + const getRes = await authenticatedTestClient(userToken).get( + `/api/v1/actors/${actorId}` + ); + expect(getRes.status).toBe(404); + }); + + test('unauthenticated request returns 401', async () => { + const createRes = await authenticatedTestClient(userToken) + .post('/api/v1/actors') + .send({ projectId, name: 'ToDeleteAnon' }); + + const response = await testClient.delete( + `/api/v1/actors/${createRes.body.id}` + ); + + expect(response.status).toBe(401); + }); + + test('returns 404 for non-existent actor', async () => { + const response = await authenticatedTestClient(adminToken).delete( + '/api/v1/actors/act_nonexistent' + ); + + expect(response.status).toBe(404); + }); + }); + + describe('PATCH /api/v1/actors/:id', () => { + let actorId: string; + + beforeAll(async () => { + const res = await authenticatedTestClient(userToken) + .post('/api/v1/actors') + .send({ projectId, name: 'UpdateMe', type: 'customer' }); + actorId = res.body.id; + }); + + test('user with permission can update an actor name', async () => { + const response = await authenticatedTestClient(userToken) + .patch(`/api/v1/actors/${actorId}`) + .send({ name: 'UpdatedName' }); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(actorId); + expect(response.body.name).toBe('UpdatedName'); + }); + + test('user can update type and externalId', async () => { + const response = await authenticatedTestClient(userToken) + .patch(`/api/v1/actors/${actorId}`) + .send({ type: 'assistant', externalId: '+15551112222' }); + + expect(response.status).toBe(200); + expect(response.body.type).toBe('assistant'); + expect(response.body.externalId).toBe('+15551112222'); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient + .patch(`/api/v1/actors/${actorId}`) + .send({ name: 'Hacked' }); + + expect(response.status).toBe(401); + }); + + test('returns 404 for non-existent actor', async () => { + const response = await authenticatedTestClient(adminToken) + .patch('/api/v1/actors/act_nonexistent') + .send({ name: 'Ghost' }); + + expect(response.status).toBe(404); + }); + }); + + describe('PATCH /api/v1/actors/:id', () => { + let actorId: string; + + beforeAll(async () => { + const res = await authenticatedTestClient(userToken) + .post('/api/v1/actors') + .send({ projectId, name: 'UpdateMe', type: 'customer' }); + actorId = res.body.id; + }); + + test('user with permission can update an actor name', async () => { + const response = await authenticatedTestClient(userToken) + .patch(`/api/v1/actors/${actorId}`) + .send({ name: 'UpdatedName' }); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(actorId); + expect(response.body.name).toBe('UpdatedName'); + }); + + test('user can update type and externalId', async () => { + const response = await authenticatedTestClient(userToken) + .patch(`/api/v1/actors/${actorId}`) + .send({ type: 'assistant', externalId: '+15559998888' }); + + expect(response.status).toBe(200); + expect(response.body.type).toBe('assistant'); + expect(response.body.externalId).toBe('+15559998888'); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient + .patch(`/api/v1/actors/${actorId}`) + .send({ name: 'Hacked' }); + + expect(response.status).toBe(401); + }); + + test('returns 404 for non-existent actor', async () => { + const response = await authenticatedTestClient(adminToken) + .patch('/api/v1/actors/act_nonexistent') + .send({ name: 'Ghost' }); + + expect(response.status).toBe(404); + }); + }); + + describe('GET /api/v1/actors with name and type filters (FEAT-9)', () => { + beforeAll(async () => { + await authenticatedTestClient(userToken) + .post('/api/v1/actors') + .send({ projectId, name: 'NameFilterAgent', type: 'agent' }); + await authenticatedTestClient(userToken) + .post('/api/v1/actors') + .send({ projectId, name: 'NameFilterCustomer', type: 'customer' }); + await authenticatedTestClient(userToken) + .post('/api/v1/actors') + .send({ projectId, name: 'Unrelated', type: 'agent' }); + }); + + test('filtering by name (partial, case-insensitive) returns matching actors', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/actors?projectId=${projectId}&name=namefilter` + ); + + expect(response.status).toBe(200); + const names = response.body.data.map((a: { name: string }) => { + return a.name; + }); + expect(names).toContain('NameFilterAgent'); + expect(names).toContain('NameFilterCustomer'); + expect(names).not.toContain('Unrelated'); + }); + + test('filtering by type returns only matching actors', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/actors?projectId=${projectId}&type=customer` + ); + + expect(response.status).toBe(200); + const types = response.body.data.map((a: { type: string }) => { + return a.type; + }); + expect( + types.every((t: string) => { + return t === 'customer'; + }) + ).toBe(true); + }); + + test('filtering by name and type combined', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/actors?projectId=${projectId}&name=namefilter&type=agent` + ); + + expect(response.status).toBe(200); + const names = response.body.data.map((a: { name: string }) => { + return a.name; + }); + expect(names).toContain('NameFilterAgent'); + expect(names).not.toContain('NameFilterCustomer'); + }); + + test('non-matching name filter returns empty array', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/actors?projectId=${projectId}&name=xyznonexistentxyz` + ); + + expect(response.status).toBe(200); + expect(response.body.data).toEqual([]); + }); + }); +}); diff --git a/packages/server/tests/unit/tests/aiProviders.test.ts b/packages/server/tests/unit/tests/aiProviders.test.ts new file mode 100644 index 00000000..653712dd --- /dev/null +++ b/packages/server/tests/unit/tests/aiProviders.test.ts @@ -0,0 +1,365 @@ +import { authenticatedTestClient, loginAs, testClient } from '../testClient'; + +describe('AI Providers', () => { + let adminToken: string; + let userToken: string; + let userId: string; + let projectId: string; + let otherProjectId: string; + let policyId: string; + let secretId: string; + + beforeAll(async () => { + // eslint-disable-next-line turbo/no-undeclared-env-vars + process.env.SECRETS_ENCRYPTION_KEY = '0'.repeat(64); + + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'aiprovadmin', password: 'supersecret' }); + + adminToken = await loginAs('aiprovadmin', 'supersecret'); + + const createUserRes = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'aiprovuser', password: 'aiprovpass' }); + + userId = createUserRes.body.id; + userToken = await loginAs('aiprovuser', 'aiprovpass'); + + const projectRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'AI Providers Test Project' }); + projectId = projectRes.body.id; + + const otherProjectRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'AI Providers Other Project' }); + otherProjectId = otherProjectRes.body.id; + + const policyRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: [ + 'aiProviders:ListAiProviders', + 'aiProviders:GetAiProvider', + 'aiProviders:CreateAiProvider', + 'aiProviders:UpdateAiProvider', + 'aiProviders:DeleteAiProvider', + ], + }); + policyId = policyRes.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId, policyId }); + + const secretRes = await authenticatedTestClient(adminToken) + .post('/api/v1/secrets') + .send({ projectId, name: 'AI Provider Secret', value: 'sk-test' }); + secretId = secretRes.body.id; + }); + + describe('GET /api/v1/ai-providers', () => { + test('authenticated user can list AI providers', async () => { + const response = await authenticatedTestClient(userToken) + .get('/api/v1/ai-providers') + .query({ projectId }); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient.get('/api/v1/ai-providers'); + expect(response.status).toBe(401); + }); + + test('user without access to project returns 403', async () => { + const response = await authenticatedTestClient(userToken) + .get('/api/v1/ai-providers') + .query({ projectId: otherProjectId }); + + expect(response.status).toBe(403); + }); + }); + + describe('POST /api/v1/ai-providers', () => { + test('authenticated user with permission can create an AI provider', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/ai-providers') + .send({ + projectId, + name: 'My OpenAI', + provider: 'openai', + defaultModel: 'gpt-4o', + }); + + expect(response.status).toBe(201); + expect(response.body.id).toBeDefined(); + expect(response.body.name).toBe('My OpenAI'); + expect(response.body.projectId).toBe(projectId); + expect(response.body.provider).toBe('openai'); + expect(response.body.defaultModel).toBe('gpt-4o'); + expect(response.body.secretId).toBeNull(); + }); + + test('can create AI provider linked to a secret', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/ai-providers') + .send({ + projectId, + secretId, + name: 'My OpenAI With Key', + provider: 'openai', + defaultModel: 'gpt-4o', + }); + + expect(response.status).toBe(201); + expect(response.body.secretId).toBe(secretId); + }); + + test('create without name returns 400', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/ai-providers') + .send({ projectId, provider: 'openai', defaultModel: 'gpt-4o' }); + + expect(response.status).toBe(400); + }); + + test('create with invalid provider returns 400', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/ai-providers') + .send({ + projectId, + name: 'x', + provider: 'invalid', + defaultModel: 'gpt-4o', + }); + + expect(response.status).toBe(400); + }); + + test('create without defaultModel returns 400', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/ai-providers') + .send({ projectId, name: 'x', provider: 'openai' }); + + expect(response.status).toBe(400); + }); + + test('create with secretId from wrong project returns 400', async () => { + const otherSecretRes = await authenticatedTestClient(adminToken) + .post('/api/v1/secrets') + .send({ projectId: otherProjectId, name: 'Other Project Secret' }); + const otherSecretId = otherSecretRes.body.id; + + const response = await authenticatedTestClient(adminToken) + .post('/api/v1/ai-providers') + .send({ + projectId, + secretId: otherSecretId, + name: 'x', + provider: 'openai', + defaultModel: 'gpt-4o', + }); + + expect(response.status).toBe(400); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient.post('/api/v1/ai-providers').send({ + projectId, + name: 'x', + provider: 'openai', + defaultModel: 'gpt-4o', + }); + + expect(response.status).toBe(401); + }); + + test('user without permission on project returns 403', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/ai-providers') + .send({ + projectId: otherProjectId, + name: 'x', + provider: 'openai', + defaultModel: 'gpt-4o', + }); + + expect(response.status).toBe(403); + }); + }); + + describe('GET /api/v1/ai-providers/:aiProviderId', () => { + let aiProviderId: string; + + beforeAll(async () => { + const res = await authenticatedTestClient(adminToken) + .post('/api/v1/ai-providers') + .send({ + projectId, + name: 'Get Test Provider', + provider: 'anthropic', + defaultModel: 'claude-3-5-haiku-latest', + }); + aiProviderId = res.body.id; + }); + + test('authenticated user with permission can get an AI provider', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/ai-providers/${aiProviderId}` + ); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(aiProviderId); + expect(response.body.projectId).toBe(projectId); + expect(response.body.provider).toBe('anthropic'); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient.get( + `/api/v1/ai-providers/${aiProviderId}` + ); + expect(response.status).toBe(401); + }); + + test('user without permission returns 403', async () => { + const adminRes = await authenticatedTestClient(adminToken) + .post('/api/v1/ai-providers') + .send({ + projectId: otherProjectId, + name: 'Other Provider', + provider: 'openai', + defaultModel: 'gpt-4o', + }); + + const response = await authenticatedTestClient(userToken).get( + `/api/v1/ai-providers/${adminRes.body.id}` + ); + expect(response.status).toBe(403); + }); + + test('unknown ID returns 404', async () => { + const response = await authenticatedTestClient(userToken).get( + '/api/v1/ai-providers/aip_doesnotexist' + ); + expect(response.status).toBe(404); + }); + }); + + describe('PATCH /api/v1/ai-providers/:aiProviderId', () => { + let aiProviderId: string; + + beforeAll(async () => { + const res = await authenticatedTestClient(adminToken) + .post('/api/v1/ai-providers') + .send({ + projectId, + name: 'Patch Test Provider', + provider: 'openai', + defaultModel: 'gpt-4o-mini', + }); + aiProviderId = res.body.id; + }); + + test('authenticated user with permission can update an AI provider', async () => { + const response = await authenticatedTestClient(userToken) + .patch(`/api/v1/ai-providers/${aiProviderId}`) + .send({ name: 'Updated Provider', defaultModel: 'gpt-4o' }); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(aiProviderId); + expect(response.body.name).toBe('Updated Provider'); + expect(response.body.defaultModel).toBe('gpt-4o'); + }); + + test('can link a secret when updating', async () => { + const response = await authenticatedTestClient(userToken) + .patch(`/api/v1/ai-providers/${aiProviderId}`) + .send({ secretId }); + + expect(response.status).toBe(200); + expect(response.body.secretId).toBe(secretId); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient + .patch(`/api/v1/ai-providers/${aiProviderId}`) + .send({ name: 'x' }); + expect(response.status).toBe(401); + }); + + test('user without permission returns 403', async () => { + const adminRes = await authenticatedTestClient(adminToken) + .post('/api/v1/ai-providers') + .send({ + projectId: otherProjectId, + name: 'Other Patch Provider', + provider: 'openai', + defaultModel: 'gpt-4o', + }); + + const response = await authenticatedTestClient(userToken) + .patch(`/api/v1/ai-providers/${adminRes.body.id}`) + .send({ name: 'x' }); + expect(response.status).toBe(403); + }); + + test('unknown ID returns 404', async () => { + const response = await authenticatedTestClient(userToken) + .patch('/api/v1/ai-providers/aip_doesnotexist') + .send({ name: 'x' }); + expect(response.status).toBe(404); + }); + }); + + describe('DELETE /api/v1/ai-providers/:aiProviderId', () => { + test('authenticated user with permission can delete an AI provider', async () => { + const createRes = await authenticatedTestClient(adminToken) + .post('/api/v1/ai-providers') + .send({ + projectId, + name: 'To Delete', + provider: 'openai', + defaultModel: 'gpt-4o', + }); + const aiProviderId = createRes.body.id; + + const response = await authenticatedTestClient(userToken).delete( + `/api/v1/ai-providers/${aiProviderId}` + ); + expect(response.status).toBe(204); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient.delete( + '/api/v1/ai-providers/aip_doesnotexist' + ); + expect(response.status).toBe(401); + }); + + test('user without permission returns 403', async () => { + const adminRes = await authenticatedTestClient(adminToken) + .post('/api/v1/ai-providers') + .send({ + projectId: otherProjectId, + name: 'Other Delete Provider', + provider: 'openai', + defaultModel: 'gpt-4o', + }); + + const response = await authenticatedTestClient(userToken).delete( + `/api/v1/ai-providers/${adminRes.body.id}` + ); + expect(response.status).toBe(403); + }); + + test('unknown ID returns 404', async () => { + const response = await authenticatedTestClient(userToken).delete( + '/api/v1/ai-providers/aip_doesnotexist' + ); + expect(response.status).toBe(404); + }); + }); +}); diff --git a/packages/server/tests/unit/tests/conversations.test.ts b/packages/server/tests/unit/tests/conversations.test.ts new file mode 100644 index 00000000..d96b8587 --- /dev/null +++ b/packages/server/tests/unit/tests/conversations.test.ts @@ -0,0 +1,567 @@ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +import { authenticatedTestClient, loginAs, testClient } from '../testClient'; + +describe('Conversations', () => { + let adminToken: string; + let userToken: string; + let userId: string; + let projectId: string; + let policyId: string; + let actorId: string; + + beforeAll(async () => { + const storageDir = fs.mkdtempSync( + path.join(os.tmpdir(), 'soat-convo-test-') + ); + process.env.FILES_STORAGE_DIR = storageDir; + process.env.EMBEDDING_PROVIDER = 'ollama'; + process.env.EMBEDDING_MODEL = 'qwen3-embedding:0.6b'; + process.env.EMBEDDING_DIMENSIONS = '1024'; + + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + adminToken = await loginAs('admin', 'supersecret'); + + const createUserRes = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'convouser', password: 'convopass' }); + + userId = createUserRes.body.id; + userToken = await loginAs('convouser', 'convopass'); + + const projectRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Conversations Test Project' }); + projectId = projectRes.body.id; + + const policyRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: [ + 'actors:CreateActor', + 'actors:ListActors', + 'actors:GetActor', + 'documents:CreateDocument', + 'documents:GetDocument', + 'conversations:ListConversations', + 'conversations:GetConversation', + 'conversations:CreateConversation', + 'conversations:UpdateConversation', + 'conversations:DeleteConversation', + ], + }); + policyId = policyRes.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId, policyId }); + + const actorRes = await authenticatedTestClient(userToken) + .post('/api/v1/actors') + .send({ projectId, name: 'ConvoActor' }); + actorId = actorRes.body.id; + }); + + describe('POST /api/v1/conversations', () => { + test('authenticated user with permission can create a conversation', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/conversations') + .send({ projectId }); + + expect(response.status).toBe(201); + expect(response.body.id).toMatch(/^conv_/); + expect(response.body.projectId).toBe(projectId); + expect(response.body.status).toBe('open'); + }); + + test('can create a conversation with closed status', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/conversations') + .send({ projectId, status: 'closed' }); + + expect(response.status).toBe(201); + expect(response.body.status).toBe('closed'); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient + .post('/api/v1/conversations') + .send({ projectId }); + + expect(response.status).toBe(401); + }); + + test('missing projectId returns 400 for JWT users', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/conversations') + .send({}); + + expect(response.status).toBe(400); + }); + }); + + describe('GET /api/v1/conversations', () => { + beforeAll(async () => { + await authenticatedTestClient(userToken) + .post('/api/v1/conversations') + .send({ projectId }); + await authenticatedTestClient(userToken) + .post('/api/v1/conversations') + .send({ projectId }); + }); + + test('authenticated user with permission can list conversations', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/conversations?projectId=${projectId}` + ); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body.data)).toBe(true); + expect(response.body.data.length).toBeGreaterThanOrEqual(2); + }); + + test('listing without projectId returns all accessible conversations', async () => { + const response = await authenticatedTestClient(userToken).get( + '/api/v1/conversations' + ); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body.data)).toBe(true); + }); + + test('can filter by actorId', async () => { + const secondActorRes = await authenticatedTestClient(userToken) + .post('/api/v1/actors') + .send({ projectId, name: 'FilterActor' }); + const secondActorId = secondActorRes.body.id; + + const convRes = await authenticatedTestClient(userToken) + .post('/api/v1/conversations') + .send({ projectId }); + const filteredConvId = convRes.body.id; + + await authenticatedTestClient(userToken) + .post(`/api/v1/conversations/${filteredConvId}/messages`) + .send({ message: 'Filter test', actorId: secondActorId }); + + const response = await authenticatedTestClient(userToken).get( + `/api/v1/conversations?actorId=${secondActorId}` + ); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body.data)).toBe(true); + expect( + response.body.data.some((c: { id: string }) => { + return c.id === filteredConvId; + }) + ).toBe(true); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient.get( + `/api/v1/conversations?projectId=${projectId}` + ); + + expect(response.status).toBe(401); + }); + }); + + describe('GET /api/v1/conversations/:id', () => { + let conversationId: string; + + beforeAll(async () => { + const res = await authenticatedTestClient(userToken) + .post('/api/v1/conversations') + .send({ projectId }); + conversationId = res.body.id; + }); + + test('user with permission can get a conversation by ID', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/conversations/${conversationId}` + ); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(conversationId); + expect(response.body.status).toBe('open'); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient.get( + `/api/v1/conversations/${conversationId}` + ); + + expect(response.status).toBe(401); + }); + + test('returns 404 for non-existent conversation', async () => { + const response = await authenticatedTestClient(adminToken).get( + '/api/v1/conversations/conv_nonexistent' + ); + + expect(response.status).toBe(404); + }); + }); + + describe('PATCH /api/v1/conversations/:id', () => { + let conversationId: string; + + beforeAll(async () => { + const res = await authenticatedTestClient(userToken) + .post('/api/v1/conversations') + .send({ projectId }); + conversationId = res.body.id; + }); + + test('user with permission can update conversation status', async () => { + const response = await authenticatedTestClient(userToken) + .patch(`/api/v1/conversations/${conversationId}`) + .send({ status: 'closed' }); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(conversationId); + expect(response.body.status).toBe('closed'); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient + .patch(`/api/v1/conversations/${conversationId}`) + .send({ status: 'open' }); + + expect(response.status).toBe(401); + }); + + test('missing status returns 400', async () => { + const response = await authenticatedTestClient(userToken) + .patch(`/api/v1/conversations/${conversationId}`) + .send({}); + + expect(response.status).toBe(400); + }); + + test('returns 404 for non-existent conversation', async () => { + const response = await authenticatedTestClient(adminToken) + .patch('/api/v1/conversations/conv_nonexistent') + .send({ status: 'open' }); + + expect(response.status).toBe(404); + }); + }); + + describe('GET /api/v1/conversations/:id/messages', () => { + let conversationId: string; + + beforeAll(async () => { + const res = await authenticatedTestClient(userToken) + .post('/api/v1/conversations') + .send({ projectId }); + conversationId = res.body.id; + }); + + test('user with permission can list messages (empty initially)', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/conversations/${conversationId}/messages` + ); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body.data)).toBe(true); + expect(response.body.data.length).toBe(0); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient.get( + `/api/v1/conversations/${conversationId}/messages` + ); + + expect(response.status).toBe(401); + }); + + test('returns 404 for non-existent conversation', async () => { + const response = await authenticatedTestClient(adminToken).get( + '/api/v1/conversations/conv_nonexistent/messages' + ); + + expect(response.status).toBe(404); + }); + }); + + describe('POST /api/v1/conversations/:id/messages', () => { + let conversationId: string; + let addedDocumentId: string; + + beforeAll(async () => { + const res = await authenticatedTestClient(userToken) + .post('/api/v1/conversations') + .send({ projectId }); + conversationId = res.body.id; + }); + + test('user with permission can add a message to a conversation', async () => { + const response = await authenticatedTestClient(userToken) + .post(`/api/v1/conversations/${conversationId}/messages`) + .send({ message: 'Hello world', actorId }); + + expect(response.status).toBe(201); + expect(response.body.documentId).toMatch(/^doc_/); + expect(response.body.actorId).toBe(actorId); + expect(typeof response.body.position).toBe('number'); + expect(response.body.content).toBe('Hello world'); + addedDocumentId = response.body.documentId; + }); + + test('message appears in list after adding', async () => { + const listRes = await authenticatedTestClient(userToken).get( + `/api/v1/conversations/${conversationId}/messages` + ); + + expect(listRes.status).toBe(200); + expect( + listRes.body.data.some((m: { documentId: string }) => { + return m.documentId === addedDocumentId; + }) + ).toBe(true); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient + .post(`/api/v1/conversations/${conversationId}/messages`) + .send({ message: 'test' }); + + expect(response.status).toBe(401); + }); + + test('missing message returns 400', async () => { + const response = await authenticatedTestClient(userToken) + .post(`/api/v1/conversations/${conversationId}/messages`) + .send({}); + + expect(response.status).toBe(400); + }); + + test('returns 404 for non-existent conversation', async () => { + const response = await authenticatedTestClient(adminToken) + .post('/api/v1/conversations/conv_nonexistent/messages') + .send({ message: 'Hello world', actorId }); + + expect(response.status).toBe(404); + }); + }); + + describe('DELETE /api/v1/conversations/:id/messages/:documentId', () => { + let conversationId: string; + let secondDocumentId: string; + + beforeAll(async () => { + const convRes = await authenticatedTestClient(userToken) + .post('/api/v1/conversations') + .send({ projectId }); + conversationId = convRes.body.id; + + const msgRes = await authenticatedTestClient(userToken) + .post(`/api/v1/conversations/${conversationId}/messages`) + .send({ message: 'Remove me', actorId }); + secondDocumentId = msgRes.body.documentId; + }); + + test('user with permission can remove a message', async () => { + const deleteRes = await authenticatedTestClient(userToken).delete( + `/api/v1/conversations/${conversationId}/messages/${secondDocumentId}` + ); + + expect(deleteRes.status).toBe(204); + + const listRes = await authenticatedTestClient(userToken).get( + `/api/v1/conversations/${conversationId}/messages` + ); + expect( + listRes.body.data.some((m: { documentId: string }) => { + return m.documentId === secondDocumentId; + }) + ).toBe(false); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient.delete( + `/api/v1/conversations/${conversationId}/messages/doc_nonexistent` + ); + + expect(response.status).toBe(401); + }); + + test('returns 404 for non-existent message', async () => { + const response = await authenticatedTestClient(adminToken).delete( + `/api/v1/conversations/${conversationId}/messages/doc_nonexistent` + ); + + expect(response.status).toBe(404); + }); + }); + + describe('DELETE /api/v1/conversations/:id', () => { + test('user with permission can delete a conversation', async () => { + const createRes = await authenticatedTestClient(userToken) + .post('/api/v1/conversations') + .send({ projectId }); + const conversationId = createRes.body.id; + + const deleteRes = await authenticatedTestClient(userToken).delete( + `/api/v1/conversations/${conversationId}` + ); + + expect(deleteRes.status).toBe(204); + + const getRes = await authenticatedTestClient(userToken).get( + `/api/v1/conversations/${conversationId}` + ); + expect(getRes.status).toBe(404); + }); + + test('unauthenticated request returns 401', async () => { + const createRes = await authenticatedTestClient(userToken) + .post('/api/v1/conversations') + .send({ projectId }); + + const response = await testClient.delete( + `/api/v1/conversations/${createRes.body.id}` + ); + + expect(response.status).toBe(401); + }); + + test('returns 404 for non-existent conversation', async () => { + const response = await authenticatedTestClient(adminToken).delete( + '/api/v1/conversations/conv_nonexistent' + ); + + expect(response.status).toBe(404); + }); + }); + + describe('GET /api/v1/conversations/:id/actors', () => { + let conversationId: string; + let secondActorIdForActorsTest: string; + + beforeAll(async () => { + const convRes = await authenticatedTestClient(userToken) + .post('/api/v1/conversations') + .send({ projectId }); + conversationId = convRes.body.id; + + const actorRes = await authenticatedTestClient(userToken) + .post('/api/v1/actors') + .send({ projectId, name: 'SecondActorForActors' }); + secondActorIdForActorsTest = actorRes.body.id; + + await authenticatedTestClient(userToken) + .post(`/api/v1/conversations/${conversationId}/messages`) + .send({ message: 'Message from actor 1', actorId }); + await authenticatedTestClient(userToken) + .post(`/api/v1/conversations/${conversationId}/messages`) + .send({ + message: 'Message from actor 2', + actorId: secondActorIdForActorsTest, + }); + await authenticatedTestClient(userToken) + .post(`/api/v1/conversations/${conversationId}/messages`) + .send({ message: 'Another from actor 1', actorId }); + }); + + test('returns distinct actors who sent messages', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/conversations/${conversationId}/actors` + ); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + expect(response.body.length).toBe(2); + const ids = response.body.map((a: { id: string }) => { + return a.id; + }); + expect(ids).toContain(actorId); + expect(ids).toContain(secondActorIdForActorsTest); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient.get( + `/api/v1/conversations/${conversationId}/actors` + ); + + expect(response.status).toBe(401); + }); + + test('returns 404 for non-existent conversation', async () => { + const response = await authenticatedTestClient(adminToken).get( + '/api/v1/conversations/conv_nonexistent/actors' + ); + + expect(response.status).toBe(404); + }); + }); + + describe('Message content field', () => { + let conversationId: string; + + beforeAll(async () => { + const convRes = await authenticatedTestClient(userToken) + .post('/api/v1/conversations') + .send({ projectId }); + conversationId = convRes.body.id; + + await authenticatedTestClient(userToken) + .post(`/api/v1/conversations/${conversationId}/messages`) + .send({ message: 'Content check message', actorId }); + }); + + test('listed messages include content field', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/conversations/${conversationId}/messages` + ); + + expect(response.status).toBe(200); + expect(response.body.data.length).toBeGreaterThanOrEqual(1); + expect(response.body.data[0].content).toBe('Content check message'); + }); + }); + + describe('Message removal cleans up document', () => { + let conversationId: string; + let documentId: string; + + beforeAll(async () => { + const convRes = await authenticatedTestClient(userToken) + .post('/api/v1/conversations') + .send({ projectId }); + conversationId = convRes.body.id; + + const msgRes = await authenticatedTestClient(userToken) + .post(`/api/v1/conversations/${conversationId}/messages`) + .send({ message: 'Orphan test message', actorId }); + documentId = msgRes.body.documentId; + }); + + test('removing a message also deletes the underlying document', async () => { + // Verify document exists before removal + const docBefore = await authenticatedTestClient(userToken).get( + `/api/v1/documents/${documentId}` + ); + expect(docBefore.status).toBe(200); + + // Remove the message + const deleteRes = await authenticatedTestClient(userToken).delete( + `/api/v1/conversations/${conversationId}/messages/${documentId}` + ); + expect(deleteRes.status).toBe(204); + + // Verify document is also gone + const docAfter = await authenticatedTestClient(userToken).get( + `/api/v1/documents/${documentId}` + ); + expect(docAfter.status).toBe(404); + }); + }); +}); diff --git a/packages/server/tests/unit/tests/documents.test.ts b/packages/server/tests/unit/tests/documents.test.ts new file mode 100644 index 00000000..ad1b8746 --- /dev/null +++ b/packages/server/tests/unit/tests/documents.test.ts @@ -0,0 +1,513 @@ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +import { authenticatedTestClient, loginAs, testClient } from '../testClient'; + +describe('Documents', () => { + let adminToken: string; + let userToken: string; + let userId: string; + let projectId: string; + let policyId: string; + let storageDir: string; + + beforeAll(async () => { + storageDir = fs.mkdtempSync(path.join(os.tmpdir(), 'soat-docs-test-')); + + process.env.FILES_STORAGE_DIR = storageDir; + + process.env.EMBEDDING_PROVIDER = 'ollama'; + + process.env.EMBEDDING_MODEL = 'qwen3-embedding:0.6b'; + + process.env.EMBEDDING_DIMENSIONS = '1024'; + + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + adminToken = await loginAs('admin', 'supersecret'); + + const createUserRes = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'docsuser', password: 'docspass' }); + + userId = createUserRes.body.id; + userToken = await loginAs('docsuser', 'docspass'); + + const projectRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Docs Test Project' }); + projectId = projectRes.body.id; + + const policyRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: [ + 'documents:ListDocuments', + 'documents:GetDocument', + 'documents:CreateDocument', + 'documents:DeleteDocument', + 'documents:SearchDocuments', + 'documents:UpdateDocument', + ], + }); + policyId = policyRes.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId, policyId }); + }); + + afterAll(() => { + fs.rmSync(storageDir, { recursive: true, force: true }); + }); + + describe('POST /api/v1/documents', () => { + test('authenticated user with permission can create a document', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/documents') + .send({ + projectId, + content: 'Hello, world! This is a test document.', + filename: 'hello.txt', + }); + + expect(response.status).toBe(201); + expect(response.body.id).toMatch(/^doc_/); + expect(response.body.filename).toBe('hello.txt'); + expect(response.body.projectId).toBe(projectId); + expect(response.body.size).toBeGreaterThan(0); + expect(response.body.content).toBeUndefined(); + }); + + test('unauthenticated request cannot create a document', async () => { + const response = await testClient.post('/api/v1/documents').send({ + projectId, + content: 'Secret', + }); + + expect(response.status).toBe(401); + }); + + test('missing projectId returns 400', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/documents') + .send({ content: 'No project' }); + + expect(response.status).toBe(400); + }); + + test('missing content returns 400', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/documents') + .send({ projectId }); + + expect(response.status).toBe(400); + }); + }); + + describe('GET /api/v1/documents', () => { + test('authenticated user with permission can list documents', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/documents?projectId=${projectId}` + ); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body.data)).toBe(true); + }); + + test('unauthenticated request cannot list documents', async () => { + const response = await testClient.get( + `/api/v1/documents?projectId=${projectId}` + ); + + expect(response.status).toBe(401); + }); + + test('listing without projectId returns all accessible documents', async () => { + const response = + await authenticatedTestClient(userToken).get('/api/v1/documents'); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body.data)).toBe(true); + }); + }); + + describe('GET /api/v1/documents/:id', () => { + let documentId: string; + + beforeAll(async () => { + const res = await authenticatedTestClient(userToken) + .post('/api/v1/documents') + .send({ + projectId, + content: 'Fetch this document back.', + filename: 'fetch-me.txt', + }); + documentId = res.body.id; + }); + + test('user with permission can get a document by ID including content', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/documents/${documentId}` + ); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(documentId); + expect(response.body.content).toBe('Fetch this document back.'); + }); + + test('unauthenticated request cannot get a document', async () => { + const response = await testClient.get(`/api/v1/documents/${documentId}`); + + expect(response.status).toBe(401); + }); + + test('returns 404 for non-existent document', async () => { + const response = await authenticatedTestClient(adminToken).get( + '/api/v1/documents/doc_nonexistent' + ); + + expect(response.status).toBe(404); + }); + }); + + describe('POST /api/v1/documents/search', () => { + beforeAll(async () => { + await authenticatedTestClient(userToken).post('/api/v1/documents').send({ + projectId, + content: 'The capital of France is Paris.', + filename: 'france.txt', + }); + }); + + test('user with permission can search documents', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/documents/search') + .send({ projectId, query: 'capital of France' }); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + }); + + test('search with limit returns at most limit results', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/documents/search') + .send({ projectId, query: 'test content', limit: 1 }); + + expect(response.status).toBe(200); + expect(response.body.length).toBeLessThanOrEqual(1); + }); + + test('unauthenticated request cannot search documents', async () => { + const response = await testClient + .post('/api/v1/documents/search') + .send({ projectId, query: 'test' }); + + expect(response.status).toBe(401); + }); + + test('search without projectId returns results across accessible projects', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/documents/search') + .send({ query: 'no project' }); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + }); + + test('missing query returns 400', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/documents/search') + .send({ projectId }); + + expect(response.status).toBe(400); + }); + + test('search results include score and content fields', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/documents/search') + .send({ projectId, query: 'capital of France' }); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + if (response.body.length > 0) { + expect(typeof response.body[0].score).toBe('number'); + expect(typeof response.body[0].content).toBe('string'); + } + }); + + test('search with threshold filters low-score results', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/documents/search') + .send({ projectId, query: 'capital of France', threshold: 0.99 }); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + for (const doc of response.body) { + expect(doc.score).toBeGreaterThanOrEqual(0.99); + } + }); + }); + + describe('DELETE /api/v1/documents/:id', () => { + test('user with permission can delete a document and file is removed from disk', async () => { + const createRes = await authenticatedTestClient(userToken) + .post('/api/v1/documents') + .send({ + projectId, + content: 'Delete me please.', + filename: 'todelete.txt', + }); + const documentId = createRes.body.id; + + const filesOnDisk = fs.readdirSync(storageDir); + expect(filesOnDisk.length).toBeGreaterThan(0); + + const deleteRes = await authenticatedTestClient(userToken).delete( + `/api/v1/documents/${documentId}` + ); + + expect(deleteRes.status).toBe(204); + + const getRes = await authenticatedTestClient(userToken).get( + `/api/v1/documents/${documentId}` + ); + expect(getRes.status).toBe(404); + }); + + test('unauthenticated request cannot delete a document', async () => { + const createRes = await authenticatedTestClient(userToken) + .post('/api/v1/documents') + .send({ projectId, content: 'Protected.' }); + const documentId = createRes.body.id; + + const response = await testClient.delete( + `/api/v1/documents/${documentId}` + ); + + expect(response.status).toBe(401); + }); + + test('returns 404 when deleting a non-existent document', async () => { + const response = await authenticatedTestClient(adminToken).delete( + '/api/v1/documents/doc_nonexistent' + ); + + expect(response.status).toBe(404); + }); + }); + + describe('POST /api/v1/documents with title, metadata, tags (FEAT-13)', () => { + test('creates a document with title, metadata, and tags', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/documents') + .send({ + projectId, + content: 'Tagged document content.', + filename: 'tagged.txt', + title: 'My Title', + metadata: { source: 'test' }, + tags: ['alpha', 'beta'], + }); + + expect(response.status).toBe(201); + expect(response.body.title).toBe('My Title'); + expect(response.body.metadata).toEqual({ source: 'test' }); + expect(response.body.tags).toEqual( + expect.arrayContaining(['alpha', 'beta']) + ); + }); + }); + + describe('PATCH /api/v1/documents/:id (FEAT-2)', () => { + let documentId: string; + + beforeAll(async () => { + const res = await authenticatedTestClient(userToken) + .post('/api/v1/documents') + .send({ + projectId, + content: 'Original content.', + filename: 'patchme.txt', + title: 'Original Title', + tags: ['initial'], + }); + documentId = res.body.id; + }); + + test('updates title only', async () => { + const response = await authenticatedTestClient(userToken) + .patch(`/api/v1/documents/${documentId}`) + .send({ title: 'Updated Title' }); + + expect(response.status).toBe(200); + expect(response.body.title).toBe('Updated Title'); + }); + + test('updates content and re-embeds', async () => { + const response = await authenticatedTestClient(userToken) + .patch(`/api/v1/documents/${documentId}`) + .send({ content: 'Updated content for re-embedding.' }); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(documentId); + }); + + test('updates tags', async () => { + const response = await authenticatedTestClient(userToken) + .patch(`/api/v1/documents/${documentId}`) + .send({ tags: ['new-tag', 'another'] }); + + expect(response.status).toBe(200); + expect(response.body.tags).toEqual( + expect.arrayContaining(['new-tag', 'another']) + ); + }); + + test('updates metadata', async () => { + const response = await authenticatedTestClient(userToken) + .patch(`/api/v1/documents/${documentId}`) + .send({ metadata: { updated: true } }); + + expect(response.status).toBe(200); + expect(response.body.metadata).toEqual({ updated: true }); + }); + + test('returns 404 for non-existent document', async () => { + const response = await authenticatedTestClient(adminToken) + .patch('/api/v1/documents/doc_nonexistent') + .send({ title: 'Ghost' }); + + expect(response.status).toBe(404); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient + .patch(`/api/v1/documents/${documentId}`) + .send({ title: 'No Auth' }); + + expect(response.status).toBe(401); + }); + + test('user without UpdateDocument permission returns 403', async () => { + // Create a second user with no UpdateDocument permission + const createRes = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'noupdate', password: 'nopass' }); + const noUpdateUserId = createRes.body.id; + const noUpdateToken = await loginAs('noupdate', 'nopass'); + + const policyRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['documents:GetDocument'], + }); + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId: noUpdateUserId, policyId: policyRes.body.id }); + + const response = await authenticatedTestClient(noUpdateToken) + .patch(`/api/v1/documents/${documentId}`) + .send({ title: 'Forbidden' }); + + expect(response.status).toBe(403); + }); + }); + + describe('POST /api/v1/documents/search with tags (FEAT-13)', () => { + let taggedDocId: string; + + beforeAll(async () => { + const res = await authenticatedTestClient(userToken) + .post('/api/v1/documents') + .send({ + projectId, + content: 'Tag-filtered search document.', + filename: 'tag-search.txt', + tags: ['unique-search-tag'], + }); + taggedDocId = res.body.id; + }); + + test('search by matching tag returns tagged document', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/documents/search') + .send({ + projectId, + query: 'tag-filtered', + tags: ['unique-search-tag'], + }); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + const ids = response.body.map((d: { id: string }) => { + return d.id; + }); + expect(ids).toContain(taggedDocId); + }); + + test('search by non-matching tag returns empty results', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/documents/search') + .send({ projectId, query: 'tag-filtered', tags: ['no-such-tag-xyz'] }); + + expect(response.status).toBe(200); + const ids = response.body.map((d: { id: string }) => { + return d.id; + }); + expect(ids).not.toContain(taggedDocId); + }); + }); + + describe('project key access', () => { + let projectKey: string; + + beforeAll(async () => { + const policyRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['documents:ListDocuments', 'documents:SearchDocuments'], + }); + const projectKeyPolicyId = policyRes.body.id; + + const projectKeyRes = await authenticatedTestClient(userToken) + .post('/api/v1/project-keys') + .send({ + projectId, + policyId: projectKeyPolicyId, + name: 'Docs Test project key', + }); + projectKey = projectKeyRes.body.key; + + await authenticatedTestClient(userToken).post('/api/v1/documents').send({ + projectId, + content: 'project key test document.', + filename: 'projectkey-doc.txt', + }); + }); + + test('project key can list documents without providing projectId', async () => { + const response = await testClient + .get('/api/v1/documents') + .set('Authorization', `Bearer ${projectKey}`); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body.data)).toBe(true); + expect(response.body.data.length).toBeGreaterThan(0); + }); + + test('project key can search documents without providing projectId', async () => { + const response = await testClient + .post('/api/v1/documents/search') + .set('Authorization', `Bearer ${projectKey}`) + .send({ query: 'project key test' }); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + }); + }); +}); diff --git a/packages/server/tests/unit/tests/files.test.ts b/packages/server/tests/unit/tests/files.test.ts new file mode 100644 index 00000000..bc735746 --- /dev/null +++ b/packages/server/tests/unit/tests/files.test.ts @@ -0,0 +1,461 @@ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +import { authenticatedTestClient, loginAs, testClient } from '../testClient'; + +describe('Files', () => { + let adminToken: string; + let userToken: string; + let userId: string; + let projectId: string; + let policyId: string; + let storageDir: string; + + beforeAll(async () => { + storageDir = fs.mkdtempSync(path.join(os.tmpdir(), 'soat-files-test-')); + // eslint-disable-next-line turbo/no-undeclared-env-vars + process.env.FILES_STORAGE_DIR = storageDir; + + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + adminToken = await loginAs('admin', 'supersecret'); + + const createUserRes = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'filesuser', password: 'filespass' }); + + userId = createUserRes.body.id; + userToken = await loginAs('filesuser', 'filespass'); + + const projectRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Files Test Project' }); + projectId = projectRes.body.id; + + const policyRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: [ + 'files:UploadFile', + 'files:GetFile', + 'files:DownloadFile', + 'files:UpdateFileMetadata', + 'files:DeleteFile', + 'files:CreateFile', + ], + }); + policyId = policyRes.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId, policyId }); + }); + + afterAll(() => { + fs.rmSync(storageDir, { recursive: true, force: true }); + }); + + describe('POST /api/v1/files/upload', () => { + test('authenticated user with permission can upload a file', async () => { + const fileContent = Buffer.from('Hello, world!'); + + const response = await authenticatedTestClient(userToken) + .post('/api/v1/files/upload') + .attach('file', fileContent, { + filename: 'hello.txt', + contentType: 'text/plain', + }) + .field('projectId', projectId); + + expect(response.status).toBe(201); + expect(response.body.id).toBeDefined(); + expect(response.body.filename).toBe('hello.txt'); + expect(response.body.contentType).toBe('text/plain'); + expect(response.body.size).toBe(fileContent.length); + }); + + test('unauthenticated request cannot upload', async () => { + const fileContent = Buffer.from('data'); + + const response = await testClient + .post('/api/v1/files/upload') + .attach('file', fileContent, { filename: 'data.txt' }) + .field('projectId', projectId); + + expect(response.status).toBe(401); + }); + + test('upload without file returns 400', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/files/upload') + .send({ projectId }); + + expect(response.status).toBe(400); + }); + + test('upload without projectId returns 400', async () => { + const fileContent = Buffer.from('data'); + + const response = await authenticatedTestClient(userToken) + .post('/api/v1/files/upload') + .attach('file', fileContent, { filename: 'data.txt' }); + + expect(response.status).toBe(400); + }); + }); + + describe('GET /api/v1/files/:id', () => { + let fileId: string; + + beforeAll(async () => { + const fileContent = Buffer.from('Get me!'); + const res = await authenticatedTestClient(userToken) + .post('/api/v1/files/upload') + .attach('file', fileContent, { + filename: 'getme.txt', + contentType: 'text/plain', + }) + .field('projectId', projectId); + fileId = res.body.id; + }); + + test('user with permission can get a file by ID', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/files/${fileId}` + ); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(fileId); + expect(response.body.filename).toBe('getme.txt'); + }); + + test('unauthenticated request cannot get a file', async () => { + const response = await testClient.get(`/api/v1/files/${fileId}`); + + expect(response.status).toBe(401); + }); + + test('returns 404 for non-existent file', async () => { + const response = await authenticatedTestClient(adminToken).get( + '/api/v1/files/nonexistent-file-id' + ); + + expect(response.status).toBe(404); + }); + }); + + describe('GET /api/v1/files/:id/download', () => { + let fileId: string; + const originalContent = 'Download me, please!'; + + beforeAll(async () => { + const fileContent = Buffer.from(originalContent); + const res = await authenticatedTestClient(userToken) + .post('/api/v1/files/upload') + .attach('file', fileContent, { + filename: 'download.txt', + contentType: 'text/plain', + }) + .field('projectId', projectId); + fileId = res.body.id; + }); + + test('user with permission can download a file', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/files/${fileId}/download` + ); + + expect(response.status).toBe(200); + expect(response.text).toBe(originalContent); + expect(response.headers['content-type']).toMatch(/text\/plain/); + expect(response.headers['content-disposition']).toMatch( + /attachment; filename="download.txt"/ + ); + }); + + test('unauthenticated request cannot download a file', async () => { + const response = await testClient.get(`/api/v1/files/${fileId}/download`); + + expect(response.status).toBe(401); + }); + }); + + describe('PATCH /api/v1/files/:id/metadata', () => { + let fileId: string; + + beforeAll(async () => { + const fileContent = Buffer.from('Metadata target'); + const res = await authenticatedTestClient(userToken) + .post('/api/v1/files/upload') + .attach('file', fileContent, { + filename: 'meta.txt', + contentType: 'text/plain', + }) + .field('projectId', projectId); + fileId = res.body.id; + }); + + test('user with permission can update file metadata', async () => { + const newMetadata = JSON.stringify({ author: 'Alice', version: 2 }); + + const response = await authenticatedTestClient(userToken) + .patch(`/api/v1/files/${fileId}/metadata`) + .send({ metadata: newMetadata }); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(fileId); + expect(response.body.metadata).toBe(newMetadata); + }); + + test('unauthenticated request cannot update metadata', async () => { + const response = await testClient + .patch(`/api/v1/files/${fileId}/metadata`) + .send({ metadata: '{}' }); + + expect(response.status).toBe(401); + }); + + test('returns 404 for non-existent file', async () => { + const response = await authenticatedTestClient(adminToken) + .patch('/api/v1/files/nonexistent-file-id/metadata') + .send({ metadata: '{}' }); + + expect(response.status).toBe(404); + }); + }); + + describe('DELETE /api/v1/files/:id', () => { + test('user with permission can delete a file and it is removed from disk', async () => { + const fileContent = Buffer.from('Delete me!'); + const uploadRes = await authenticatedTestClient(userToken) + .post('/api/v1/files/upload') + .attach('file', fileContent, { + filename: 'todelete.txt', + contentType: 'text/plain', + }) + .field('projectId', projectId); + const fileId = uploadRes.body.id; + + // Verify file exists on disk + const filesOnDisk = fs.readdirSync(storageDir); + expect( + filesOnDisk.some((f) => { + return f.includes(fileId); + }) + ).toBe(true); + + const deleteRes = await authenticatedTestClient(userToken).delete( + `/api/v1/files/${fileId}` + ); + + expect(deleteRes.status).toBe(204); + + // Verify file is removed from disk + const filesAfter = fs.readdirSync(storageDir); + expect( + filesAfter.some((f) => { + return f.includes(fileId); + }) + ).toBe(false); + }); + + test('unauthenticated request cannot delete a file', async () => { + const fileContent = Buffer.from('Protected'); + const uploadRes = await authenticatedTestClient(userToken) + .post('/api/v1/files/upload') + .attach('file', fileContent, { filename: 'protected.txt' }) + .field('projectId', projectId); + const fileId = uploadRes.body.id; + + const response = await testClient.delete(`/api/v1/files/${fileId}`); + + expect(response.status).toBe(401); + }); + + test('returns 404 when deleting a non-existent file', async () => { + const response = await authenticatedTestClient(adminToken).delete( + '/api/v1/files/nonexistent-file-id' + ); + + expect(response.status).toBe(404); + }); + }); + + describe('GET /api/v1/files/:id/download/base64', () => { + let fileId: string; + const originalContent = 'Base64 download test content!'; + + beforeAll(async () => { + const fileContent = Buffer.from(originalContent); + const res = await authenticatedTestClient(userToken) + .post('/api/v1/files/upload') + .attach('file', fileContent, { + filename: 'base64dl.txt', + contentType: 'text/plain', + }) + .field('projectId', projectId); + fileId = res.body.id; + }); + + test('user with permission can download a file as base64', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/files/${fileId}/download/base64` + ); + + expect(response.status).toBe(200); + expect(response.body.content).toBe( + Buffer.from(originalContent).toString('base64') + ); + expect(response.body.filename).toBe('base64dl.txt'); + expect(response.body.contentType).toBe('text/plain'); + expect(response.body.size).toBe(Buffer.from(originalContent).length); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient.get( + `/api/v1/files/${fileId}/download/base64` + ); + + expect(response.status).toBe(401); + }); + + test('returns 404 for non-existent file', async () => { + const response = await authenticatedTestClient(adminToken).get( + '/api/v1/files/nonexistent-file-id/download/base64' + ); + + expect(response.status).toBe(404); + }); + }); + + describe('POST /api/v1/files/upload/base64', () => { + test('user with permission can upload a file via base64', async () => { + const content = Buffer.from('Hello base64 upload!').toString('base64'); + + const response = await authenticatedTestClient(userToken) + .post('/api/v1/files/upload/base64') + .send({ + projectId, + content, + filename: 'base64upload.txt', + contentType: 'text/plain', + }); + + expect(response.status).toBe(201); + expect(response.body.id).toBeDefined(); + expect(response.body.filename).toBe('base64upload.txt'); + expect(response.body.contentType).toBe('text/plain'); + }); + + test('unauthenticated request returns 401', async () => { + const content = Buffer.from('data').toString('base64'); + + const response = await testClient + .post('/api/v1/files/upload/base64') + .send({ projectId, content, filename: 'test.txt' }); + + expect(response.status).toBe(401); + }); + }); + + describe('PATCH /api/v1/files/:id/metadata - filename update', () => { + let fileId: string; + + beforeAll(async () => { + const fileContent = Buffer.from('Filename update target'); + const res = await authenticatedTestClient(userToken) + .post('/api/v1/files/upload') + .attach('file', fileContent, { + filename: 'original-name.txt', + contentType: 'text/plain', + }) + .field('projectId', projectId); + fileId = res.body.id; + }); + + test('user can update filename only', async () => { + const response = await authenticatedTestClient(userToken) + .patch(`/api/v1/files/${fileId}/metadata`) + .send({ filename: 'renamed-file.txt' }); + + expect(response.status).toBe(200); + expect(response.body.filename).toBe('renamed-file.txt'); + }); + + test('user can update both filename and metadata', async () => { + const newMetadata = JSON.stringify({ version: 3 }); + + const response = await authenticatedTestClient(userToken) + .patch(`/api/v1/files/${fileId}/metadata`) + .send({ filename: 'both-updated.txt', metadata: newMetadata }); + + expect(response.status).toBe(200); + expect(response.body.filename).toBe('both-updated.txt'); + expect(response.body.metadata).toBe(newMetadata); + }); + }); + + describe('GET /api/v1/files - projectId filter', () => { + let secondProjectId: string; + + beforeAll(async () => { + const projectRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Files Filter Project' }); + secondProjectId = projectRes.body.id; + + const policyRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${secondProjectId}/policies`) + .send({ permissions: ['files:UploadFile', 'files:GetFile'] }); + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${secondProjectId}/members`) + .send({ userId, policyId: policyRes.body.id }); + + await authenticatedTestClient(userToken) + .post('/api/v1/files/upload') + .attach('file', Buffer.from('proj2 file'), { + filename: 'proj2.txt', + contentType: 'text/plain', + }) + .field('projectId', secondProjectId); + }); + + test('listing with projectId returns only files in that project', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/files?projectId=${secondProjectId}` + ); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body.data)).toBe(true); + expect(response.body.data.length).toBe(1); + expect(response.body.data[0].filename).toBe('proj2.txt'); + }); + + test('listing without projectId returns files across projects', async () => { + const response = + await authenticatedTestClient(userToken).get('/api/v1/files'); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body.data)).toBe(true); + // Should include files from both projects + expect(response.body.data.length).toBeGreaterThanOrEqual(2); + }); + + test('returns 403 when requesting files for a project the user cannot access', async () => { + const forbiddenProjectRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Forbidden Files Project' }); + const forbiddenProjectId = forbiddenProjectRes.body.id; + + const response = await authenticatedTestClient(userToken).get( + `/api/v1/files?projectId=${forbiddenProjectId}` + ); + + expect(response.status).toBe(403); + }); + }); +}); diff --git a/packages/server/tests/unit/tests/iam.test.ts b/packages/server/tests/unit/tests/iam.test.ts new file mode 100644 index 00000000..2ecf912c --- /dev/null +++ b/packages/server/tests/unit/tests/iam.test.ts @@ -0,0 +1,436 @@ +import { + buildSrn, + evaluateCondition, + evaluatePolicies, + matchesPattern, + statementMatches, + validatePolicyDocument, + type PolicyDocument, +} from '../../../src/lib/iam'; + +describe('IAM', () => { + describe('validatePolicyDocument', () => { + test('valid document with Allow statement passes', () => { + const doc: PolicyDocument = { + statement: [ + { + effect: 'Allow', + action: ['files:GetFile'], + }, + ], + }; + const result = validatePolicyDocument(doc); + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + test('valid document with all fields passes', () => { + const doc: PolicyDocument = { + statement: [ + { + effect: 'Allow', + action: ['files:GetFile', 'files:*'], + resource: ['soat:proj_ABC:file:*'], + condition: { + StringEquals: { 'soat:tag:env': 'prod' }, + }, + }, + ], + }; + const result = validatePolicyDocument(doc); + expect(result.valid).toBe(true); + }); + + test('invalid effect fails', () => { + const doc = { + statement: [{ effect: 'Grant', action: ['files:GetFile'] }], + }; + const result = validatePolicyDocument(doc); + expect(result.valid).toBe(false); + expect(result.errors.some((e) => e.includes('effect'))).toBe(true); + }); + + test('empty action array fails', () => { + const doc = { statement: [{ effect: 'Allow', action: [] }] }; + const result = validatePolicyDocument(doc); + expect(result.valid).toBe(false); + expect(result.errors.some((e) => e.includes('action'))).toBe(true); + }); + + test('invalid action format fails', () => { + const doc = { + statement: [{ effect: 'Allow', action: ['invalid-action'] }], + }; + const result = validatePolicyDocument(doc); + expect(result.valid).toBe(false); + }); + + test('wildcard * action passes', () => { + const doc = { statement: [{ effect: 'Allow', action: ['*'] }] }; + const result = validatePolicyDocument(doc); + expect(result.valid).toBe(true); + }); + + test('module:* action passes', () => { + const doc = { statement: [{ effect: 'Allow', action: ['files:*'] }] }; + const result = validatePolicyDocument(doc); + expect(result.valid).toBe(true); + }); + + test('invalid SRN format fails', () => { + const doc = { + statement: [ + { effect: 'Allow', action: ['files:GetFile'], resource: ['invalid'] }, + ], + }; + const result = validatePolicyDocument(doc); + expect(result.valid).toBe(false); + expect(result.errors.some((e) => e.includes('resource'))).toBe(true); + }); + + test('valid SRN with wildcard passes', () => { + const doc = { + statement: [ + { + effect: 'Allow', + action: ['files:GetFile'], + resource: ['soat:proj_123:file:*'], + }, + ], + }; + const result = validatePolicyDocument(doc); + expect(result.valid).toBe(true); + }); + + test('invalid condition operator fails', () => { + const doc = { + statement: [ + { + effect: 'Allow', + action: ['files:GetFile'], + condition: { InvalidOp: { 'soat:tag:env': 'prod' } }, + }, + ], + }; + const result = validatePolicyDocument(doc); + expect(result.valid).toBe(false); + expect(result.errors.some((e) => e.includes('operator'))).toBe(true); + }); + + test('condition key not starting with soat: fails', () => { + const doc = { + statement: [ + { + effect: 'Allow', + action: ['files:GetFile'], + condition: { StringEquals: { env: 'prod' } }, + }, + ], + }; + const result = validatePolicyDocument(doc); + expect(result.valid).toBe(false); + }); + + test('non-object input fails', () => { + const result = validatePolicyDocument('invalid'); + expect(result.valid).toBe(false); + }); + + test('missing statement array fails', () => { + const result = validatePolicyDocument({ foo: 'bar' }); + expect(result.valid).toBe(false); + expect(result.errors.some((e) => e.includes('statement'))).toBe(true); + }); + }); + + describe('buildSrn', () => { + test('produces correct SRN string', () => { + const srn = buildSrn({ + projectPublicId: 'proj_ABC', + resourceType: 'file', + resourceId: 'file_123', + }); + expect(srn).toBe('soat:proj_ABC:file:file_123'); + }); + + test('works with different resource types', () => { + expect( + buildSrn({ + projectPublicId: 'p', + resourceType: 'document', + resourceId: 'd', + }) + ).toBe('soat:p:document:d'); + }); + }); + + describe('matchesPattern', () => { + test('* matches everything', () => { + expect(matchesPattern({ pattern: '*', value: 'files:GetFile' })).toBe( + true + ); + }); + + test('module:* matches module actions', () => { + expect( + matchesPattern({ pattern: 'files:*', value: 'files:GetFile' }) + ).toBe(true); + expect( + matchesPattern({ pattern: 'files:*', value: 'actors:GetActor' }) + ).toBe(false); + }); + + test('exact match works', () => { + expect( + matchesPattern({ pattern: 'files:GetFile', value: 'files:GetFile' }) + ).toBe(true); + expect( + matchesPattern({ pattern: 'files:GetFile', value: 'files:DeleteFile' }) + ).toBe(false); + }); + + test('SRN resource wildcard matches', () => { + expect( + matchesPattern({ + pattern: 'soat:proj_ABC:file:*', + value: 'soat:proj_ABC:file:file_123', + }) + ).toBe(true); + expect( + matchesPattern({ + pattern: 'soat:proj_ABC:file:*', + value: 'soat:proj_XYZ:file:file_123', + }) + ).toBe(false); + }); + }); + + describe('evaluateCondition', () => { + test('StringEquals passes when values match', () => { + expect( + evaluateCondition({ + condition: { StringEquals: { 'soat:tag:env': 'prod' } }, + context: { 'soat:tag:env': 'prod' }, + }) + ).toBe(true); + }); + + test('StringEquals fails when values differ', () => { + expect( + evaluateCondition({ + condition: { StringEquals: { 'soat:tag:env': 'prod' } }, + context: { 'soat:tag:env': 'dev' }, + }) + ).toBe(false); + }); + + test('StringNotEquals passes when values differ', () => { + expect( + evaluateCondition({ + condition: { StringNotEquals: { 'soat:tag:env': 'prod' } }, + context: { 'soat:tag:env': 'dev' }, + }) + ).toBe(true); + }); + + test('StringLike with glob matches', () => { + expect( + evaluateCondition({ + condition: { StringLike: { 'soat:tag:env': 'prod*' } }, + context: { 'soat:tag:env': 'production' }, + }) + ).toBe(true); + }); + + test('multiple operators require all to pass (AND logic)', () => { + expect( + evaluateCondition({ + condition: { + StringEquals: { 'soat:tag:env': 'prod' }, + StringNotEquals: { 'soat:tag:region': 'us-east-1' }, + }, + context: { 'soat:tag:env': 'prod', 'soat:tag:region': 'eu-west-1' }, + }) + ).toBe(true); + + expect( + evaluateCondition({ + condition: { + StringEquals: { 'soat:tag:env': 'prod' }, + StringNotEquals: { 'soat:tag:region': 'us-east-1' }, + }, + context: { 'soat:tag:env': 'prod', 'soat:tag:region': 'us-east-1' }, + }) + ).toBe(false); + }); + }); + + describe('statementMatches', () => { + const baseStatement = { + effect: 'Allow' as const, + action: ['files:GetFile'], + resource: ['soat:proj_ABC:file:*'], + }; + + test('matches when action and resource match', () => { + expect( + statementMatches({ + statement: baseStatement, + action: 'files:GetFile', + resource: 'soat:proj_ABC:file:file_123', + context: {}, + }) + ).toBe(true); + }); + + test('does not match wrong action', () => { + expect( + statementMatches({ + statement: baseStatement, + action: 'files:DeleteFile', + resource: 'soat:proj_ABC:file:file_123', + context: {}, + }) + ).toBe(false); + }); + + test('does not match wrong resource', () => { + expect( + statementMatches({ + statement: baseStatement, + action: 'files:GetFile', + resource: 'soat:proj_XYZ:file:file_123', + context: {}, + }) + ).toBe(false); + }); + + test('statement without resource matches any resource', () => { + const stmt = { effect: 'Allow' as const, action: ['files:GetFile'] }; + expect( + statementMatches({ + statement: stmt, + action: 'files:GetFile', + resource: 'soat:proj_ABC:file:file_123', + context: {}, + }) + ).toBe(true); + }); + + test('does not match when condition fails', () => { + const stmt = { + ...baseStatement, + condition: { StringEquals: { 'soat:tag:env': 'prod' } }, + }; + expect( + statementMatches({ + statement: stmt, + action: 'files:GetFile', + resource: 'soat:proj_ABC:file:file_123', + context: { 'soat:tag:env': 'dev' }, + }) + ).toBe(false); + }); + }); + + describe('evaluatePolicies', () => { + const allowPolicy: PolicyDocument = { + statement: [{ effect: 'Allow', action: ['files:GetFile'] }], + }; + + const denyPolicy: PolicyDocument = { + statement: [{ effect: 'Deny', action: ['files:GetFile'] }], + }; + + test('returns false when no policies', () => { + expect(evaluatePolicies({ policies: [], action: 'files:GetFile' })).toBe( + false + ); + }); + + test('returns false when no matching statements', () => { + expect( + evaluatePolicies({ + policies: [allowPolicy], + action: 'files:DeleteFile', + }) + ).toBe(false); + }); + + test('returns true when Allow matches', () => { + expect( + evaluatePolicies({ policies: [allowPolicy], action: 'files:GetFile' }) + ).toBe(true); + }); + + test('explicit Deny overrides Allow (Deny wins)', () => { + expect( + evaluatePolicies({ + policies: [allowPolicy, denyPolicy], + action: 'files:GetFile', + }) + ).toBe(false); + }); + + test('Deny short-circuits even if Allow comes first', () => { + expect( + evaluatePolicies({ + policies: [denyPolicy, allowPolicy], + action: 'files:GetFile', + }) + ).toBe(false); + }); + + test('resource filtering works', () => { + const scopedPolicy: PolicyDocument = { + statement: [ + { + effect: 'Allow', + action: ['files:GetFile'], + resource: ['soat:proj_A:file:*'], + }, + ], + }; + expect( + evaluatePolicies({ + policies: [scopedPolicy], + action: 'files:GetFile', + resource: 'soat:proj_A:file:file_123', + }) + ).toBe(true); + expect( + evaluatePolicies({ + policies: [scopedPolicy], + action: 'files:GetFile', + resource: 'soat:proj_B:file:file_123', + }) + ).toBe(false); + }); + + test('condition filtering works', () => { + const condPolicy: PolicyDocument = { + statement: [ + { + effect: 'Allow', + action: ['files:GetFile'], + condition: { StringEquals: { 'soat:tag:env': 'prod' } }, + }, + ], + }; + expect( + evaluatePolicies({ + policies: [condPolicy], + action: 'files:GetFile', + context: { 'soat:tag:env': 'prod' }, + }) + ).toBe(true); + expect( + evaluatePolicies({ + policies: [condPolicy], + action: 'files:GetFile', + context: { 'soat:tag:env': 'dev' }, + }) + ).toBe(false); + }); + }); +}); diff --git a/packages/server/tests/unit/tests/mcp.test.ts b/packages/server/tests/unit/tests/mcp.test.ts new file mode 100644 index 00000000..8ec2ea1c --- /dev/null +++ b/packages/server/tests/unit/tests/mcp.test.ts @@ -0,0 +1,86 @@ +import { authenticatedTestClient, loginAs, testClient } from '../testClient'; + +describe('MCP tools/list', () => { + test('registers the expected tools', async () => { + const res = await testClient + .post('/mcp') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json, text/event-stream') + .send({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }); + + expect(res.status).toBe(200); + const tools: { name: string }[] = res.body.result.tools; + const names = tools.map((t) => { + return t.name; + }); + expect(names).toContain('list-files'); + expect(names).toContain('get-file'); + expect(names).toContain('create-file'); + expect(names).toContain('delete-file'); + expect(names).toContain('update-actor'); + expect(names).toContain('upload-file'); + expect(names).toContain('download-file'); + expect(names).toContain('update-file-metadata'); + }); +}); + +describe('MCP get-* tools with nonexistent ids', () => { + let adminToken: string; + + beforeAll(async () => { + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'mcpadmin', password: 'mcppass' }); + adminToken = await loginAs('mcpadmin', 'mcppass'); + }); + + const mcpCall = ( + token: string, + toolName: string, + args: Record + ) => { + return authenticatedTestClient(token) + .post('/mcp') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json, text/event-stream') + .send({ + jsonrpc: '2.0', + id: 2, + method: 'tools/call', + params: { name: toolName, arguments: args }, + }); + }; + + test('get-document with nonexistent id returns structured JSON error', async () => { + const res = await mcpCall(adminToken, 'get-document', { + id: 'doc_nonexistent_xyz', + }); + expect(res.status).toBe(200); + const text = res.body.result?.content?.[0]?.text; + expect(text).toBeDefined(); + const parsed = JSON.parse(text); + expect(parsed.error).toBe('not_found'); + }); + + test('get-actor with nonexistent id returns structured JSON error', async () => { + const res = await mcpCall(adminToken, 'get-actor', { + id: 'actor_nonexistent_xyz', + }); + expect(res.status).toBe(200); + const text = res.body.result?.content?.[0]?.text; + expect(text).toBeDefined(); + const parsed = JSON.parse(text); + expect(parsed.error).toBe('not_found'); + }); + + test('get-conversation with nonexistent id returns structured JSON error', async () => { + const res = await mcpCall(adminToken, 'get-conversation', { + id: 'conv_nonexistent_xyz', + }); + expect(res.status).toBe(200); + const text = res.body.result?.content?.[0]?.text; + expect(text).toBeDefined(); + const parsed = JSON.parse(text); + expect(parsed.error).toBe('not_found'); + }); +}); diff --git a/packages/server/tests/unit/tests/permissionsFlow.test.ts b/packages/server/tests/unit/tests/permissionsFlow.test.ts new file mode 100644 index 00000000..54ecc723 --- /dev/null +++ b/packages/server/tests/unit/tests/permissionsFlow.test.ts @@ -0,0 +1,1069 @@ +import { authenticatedTestClient, loginAs, testClient } from '../testClient'; + +/** + * Integration test for the full IAM permissions flow: + * + * 1. admin creates a project + * 2. admin creates a regular user + * 3. admin assigns the user to the project with a read-only policy + * 4. user can read a file using JWT + * 5. user cannot delete that file using JWT + * 6. user creates an project key scoped to the project + * 7. user assigns the deleteFile action to the project key + * 8. user can read the file using the project key + * 9. user cannot delete the file using the project key (membership policy intersection) + */ + +// ─── Group 1: Setup (Steps 1-3) ────────────────────────────────────────── + +describe('Group 1: Setup - Admin creates project, user, and assigns permissions', () => { + let adminToken: string; + let projectId: string; + let userId: string; + + beforeAll(async () => { + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + adminToken = await loginAs('admin', 'supersecret'); + + // Create project + const projectResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Test Project' }); + projectId = projectResponse.body.id; + + // Create user + const userResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'alice', password: 'alicepass' }); + userId = userResponse.body.id; + }); + + test('admin can create a project', async () => { + const response = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Test Project' }); + + expect(response.status).toBe(201); + expect(response.body.id).toBeDefined(); + expect(response.body.name).toBe('Test Project'); + }); + + test('admin can create a regular user', async () => { + const response = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'alice2', password: 'alicepass' }); + + expect(response.status).toBe(201); + expect(response.body.id).toBeDefined(); + expect(response.body.username).toBe('alice2'); + expect(response.body.role).toBe('user'); + }); + + test('admin creates a read-only policy for the project', async () => { + const response = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['files:GetFile'], + notPermissions: ['files:DeleteFile'], + }); + + expect(response.status).toBe(201); + expect(response.body.id).toBeDefined(); + expect(response.body.permissions).toEqual(['files:GetFile']); + expect(response.body.notPermissions).toEqual(['files:DeleteFile']); + }); + + test('admin adds user to project with the read-only policy', async () => { + // First create the policy + const policyResponse = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['files:GetFile'], + notPermissions: ['files:DeleteFile'], + }); + const policyId = policyResponse.body.id; + + const response = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ + userId, + policyId, + }); + + expect(response.status).toBe(201); + }); +}); + +// ─── Group 2: JWT Permissions (Steps 4-5) ───────────────────────────────── + +describe('Group 2: JWT Permissions - User can read but not delete file', () => { + let adminToken: string; + let projectId: string; + let userId: string; + let userToken: string; + let fileId: string; + + beforeAll(async () => { + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + adminToken = await loginAs('admin', 'supersecret'); + + // Create project + const projectResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Test Project' }); + projectId = projectResponse.body.id; + + // Create user + const userResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'charlie', password: 'charliepass' }); + userId = userResponse.body.id; + userToken = await loginAs('charlie', 'charliepass'); + + // Create policy and add user to project + const policyResponse = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['files:GetFile'], + notPermissions: ['files:DeleteFile'], + }); + const policyId = policyResponse.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ + userId, + policyId, + }); + + // Create file in project + const fileResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/files') + .send({ + projectId, + filename: 'test.txt', + storageType: 'local', + storagePath: '/tmp/test.txt', + }); + fileId = fileResponse.body.id; + }); + + test('user can read file with JWT', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/files/${fileId}` + ); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(fileId); + expect(response.body.filename).toBe('test.txt'); + }); + + test('user cannot delete file with JWT', async () => { + const response = await authenticatedTestClient(userToken).delete( + `/api/v1/files/${fileId}` + ); + + expect(response.status).toBe(403); + expect(response.body.error).toBe('Forbidden'); + }); +}); + +// ─── Group 3: project key Permissions (Steps 6-9) ───────────────────────────── + +describe('Group 3: project key Permissions - Create key, assign permissions, test access', () => { + let adminToken: string; + let projectId: string; + let userId: string; + let userToken: string; + let projectKey: string; + let fileId: string; + + beforeAll(async () => { + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + adminToken = await loginAs('admin', 'supersecret'); + + // Create project + const projectResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Test Project' }); + projectId = projectResponse.body.id; + + // Create user + const userResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'diana', password: 'dianapass' }); + userId = userResponse.body.id; + userToken = await loginAs('diana', 'dianapass'); + + // Create policy and add user to project + const policyResponse = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['files:GetFile', 'projects:GetProject'], + notPermissions: ['files:DeleteFile'], + }); + const policyId = policyResponse.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ + userId, + policyId, + }); + + // Create project key with delete permission + const newPolicyResponse = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['files:GetFile', 'files:DeleteFile'], + }); + const newPolicyId = newPolicyResponse.body.id; + + const projectKeyResponse = await authenticatedTestClient(userToken) + .post('/api/v1/project-keys') + .send({ + projectId, + policyId: newPolicyId, + name: 'Test project key', + }); + projectKey = projectKeyResponse.body.key; + + // Create file + const fileResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/files') + .send({ + projectId, + filename: 'test.txt', + storageType: 'local', + storagePath: '/tmp/test.txt', + }); + fileId = fileResponse.body.id; + }); + + test('user can create an project key for the project', async () => { + // First get the policy ID + const policiesResponse = await authenticatedTestClient(userToken).get( + `/api/v1/projects/${projectId}/policies` + ); + const policyId = policiesResponse.body[0].id; + + const response = await authenticatedTestClient(userToken) + .post('/api/v1/project-keys') + .send({ + projectId, + policyId, + name: 'Test project key', + }); + + expect(response.status).toBe(201); + expect(response.body.id).toBeDefined(); + expect(response.body.name).toBe('Test project key'); + expect(response.body.key).toMatch(/^sk_/); + expect(response.body.keyPrefix).toBe(response.body.key.slice(0, 8)); + }); + + test('user can read file using the project key', async () => { + const response = await testClient + .get(`/api/v1/files/${fileId}`) + .set('Authorization', `Bearer ${projectKey}`); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(fileId); + expect(response.body.filename).toBe('test.txt'); + }); + + test('user cannot delete file using the project key due to policy intersection', async () => { + const response = await testClient + .delete(`/api/v1/files/${fileId}`) + .set('Authorization', `Bearer ${projectKey}`); + + expect(response.status).toBe(403); + expect(response.body.error).toBe('Forbidden'); + }); +}); + +// ─── Group 4: Multiple Users with Different Policies ───────────────────── + +describe('Group 4: Two users in the same project with different policies', () => { + let adminToken: string; + let projectId: string; + let readerToken: string; + let editorToken: string; + let fileId: string; + + beforeAll(async () => { + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + adminToken = await loginAs('admin', 'supersecret'); + + const projectResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Multi-User Project' }); + projectId = projectResponse.body.id; + + const readerUserResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'frank', password: 'frankpass' }); + readerToken = await loginAs('frank', 'frankpass'); + + const editorUserResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'grace', password: 'gracepass' }); + editorToken = await loginAs('grace', 'gracepass'); + + const readPolicyResponse = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['files:GetFile'], + notPermissions: [], + }); + const readPolicyId = readPolicyResponse.body.id; + + const editPolicyResponse = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['files:GetFile', 'files:DeleteFile'], + notPermissions: [], + }); + const editPolicyId = editPolicyResponse.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId: readerUserResponse.body.id, policyId: readPolicyId }); + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId: editorUserResponse.body.id, policyId: editPolicyId }); + + const fileResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/files') + .send({ + projectId, + filename: 'shared.txt', + storageType: 'local', + storagePath: '/tmp/shared.txt', + }); + fileId = fileResponse.body.id; + }); + + test('reader user can read the file', async () => { + const response = await authenticatedTestClient(readerToken).get( + `/api/v1/files/${fileId}` + ); + expect(response.status).toBe(200); + expect(response.body.id).toBe(fileId); + }); + + test('reader user cannot delete the file', async () => { + const response = await authenticatedTestClient(readerToken).delete( + `/api/v1/files/${fileId}` + ); + expect(response.status).toBe(403); + expect(response.body.error).toBe('Forbidden'); + }); + + test('editor user can read the file', async () => { + const response = await authenticatedTestClient(editorToken).get( + `/api/v1/files/${fileId}` + ); + expect(response.status).toBe(200); + expect(response.body.id).toBe(fileId); + }); + + test('editor user can delete the file', async () => { + const response = await authenticatedTestClient(editorToken).delete( + `/api/v1/files/${fileId}` + ); + expect(response.status).toBe(204); + }); +}); + +// ─── Group 5: User with Multiple project keys ───────────────────────────────── + +describe('Group 5: User with multiple project keys scoped to different permissions', () => { + let adminToken: string; + let projectId: string; + let userToken: string; + let readOnlyProjectKey: string; + let deleteOnlyProjectKey: string; + let fileId: string; + + beforeAll(async () => { + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + adminToken = await loginAs('admin', 'supersecret'); + + const projectResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Multiple project keys Project' }); + projectId = projectResponse.body.id; + + const userResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'howard', password: 'howardpass' }); + userToken = await loginAs('howard', 'howardpass'); + + // User membership policy: allows both read and delete + const memberPolicyResponse = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['files:GetFile', 'files:DeleteFile'], + notPermissions: [], + }); + const memberPolicyId = memberPolicyResponse.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId: userResponse.body.id, policyId: memberPolicyId }); + + // project key policy 1: read-only + const readKeyPolicyResponse = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['files:GetFile'], + notPermissions: [], + }); + const readKeyPolicyId = readKeyPolicyResponse.body.id; + + // project key policy 2: delete-only + const deleteKeyPolicyResponse = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['files:DeleteFile'], + notPermissions: [], + }); + const deleteKeyPolicyId = deleteKeyPolicyResponse.body.id; + + const readKeyResponse = await authenticatedTestClient(userToken) + .post('/api/v1/project-keys') + .send({ projectId, policyId: readKeyPolicyId, name: 'Read Key' }); + readOnlyProjectKey = readKeyResponse.body.key; + + const deleteKeyResponse = await authenticatedTestClient(userToken) + .post('/api/v1/project-keys') + .send({ projectId, policyId: deleteKeyPolicyId, name: 'Delete Key' }); + deleteOnlyProjectKey = deleteKeyResponse.body.key; + + const fileResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/files') + .send({ + projectId, + filename: 'target.txt', + storageType: 'local', + storagePath: '/tmp/target.txt', + }); + fileId = fileResponse.body.id; + }); + + test('read-only project key can read the file', async () => { + const response = await testClient + .get(`/api/v1/files/${fileId}`) + .set('Authorization', `Bearer ${readOnlyProjectKey}`); + expect(response.status).toBe(200); + expect(response.body.id).toBe(fileId); + }); + + test('read-only project key cannot delete the file', async () => { + const response = await testClient + .delete(`/api/v1/files/${fileId}`) + .set('Authorization', `Bearer ${readOnlyProjectKey}`); + expect(response.status).toBe(403); + expect(response.body.error).toBe('Forbidden'); + }); + + test('delete-only project key cannot read the file', async () => { + const response = await testClient + .get(`/api/v1/files/${fileId}`) + .set('Authorization', `Bearer ${deleteOnlyProjectKey}`); + expect(response.status).toBe(403); + expect(response.body.error).toBe('Forbidden'); + }); + + test('delete-only project key can delete the file', async () => { + const response = await testClient + .delete(`/api/v1/files/${fileId}`) + .set('Authorization', `Bearer ${deleteOnlyProjectKey}`); + expect(response.status).toBe(204); + }); +}); + +// ─── Group 6: project key Project Isolation ────────────────────────────────── + +describe('Group 6: project key cannot access files in a different project', () => { + let adminToken: string; + let projectAId: string; + let projectBId: string; + let userToken: string; + let projectKey: string; + let fileInProjectA: string; + let fileInProjectB: string; + + beforeAll(async () => { + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + adminToken = await loginAs('admin', 'supersecret'); + + const projectAResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Project Alpha' }); + projectAId = projectAResponse.body.id; + + const projectBResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Project Beta' }); + projectBId = projectBResponse.body.id; + + const userResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'ivan', password: 'ivanpass' }); + userToken = await loginAs('ivan', 'ivanpass'); + + const policyResponse = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectAId}/policies`) + .send({ + permissions: ['files:GetFile'], + notPermissions: [], + }); + const policyId = policyResponse.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectAId}/members`) + .send({ userId: userResponse.body.id, policyId }); + + const projectKeyResponse = await authenticatedTestClient(userToken) + .post('/api/v1/project-keys') + .send({ projectId: projectAId, policyId, name: 'Project A Key' }); + projectKey = projectKeyResponse.body.key; + + const fileAResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/files') + .send({ + projectId: projectAId, + filename: 'alpha.txt', + storageType: 'local', + storagePath: '/tmp/alpha.txt', + }); + fileInProjectA = fileAResponse.body.id; + + const fileBResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/files') + .send({ + projectId: projectBId, + filename: 'beta.txt', + storageType: 'local', + storagePath: '/tmp/beta.txt', + }); + fileInProjectB = fileBResponse.body.id; + }); + + test('project key can read a file from its own project', async () => { + const response = await testClient + .get(`/api/v1/files/${fileInProjectA}`) + .set('Authorization', `Bearer ${projectKey}`); + expect(response.status).toBe(200); + expect(response.body.id).toBe(fileInProjectA); + }); + + test('project key cannot read a file from a different project', async () => { + const response = await testClient + .get(`/api/v1/files/${fileInProjectB}`) + .set('Authorization', `Bearer ${projectKey}`); + expect(response.status).toBe(403); + expect(response.body.error).toBe('Forbidden'); + }); +}); + +// ─── Group 7: Wildcard * Policy ─────────────────────────────────────────── + +describe('Group 7: Policy with wildcard * grants all permissions', () => { + let adminToken: string; + let projectId: string; + let userToken: string; + let fileId: string; + + beforeAll(async () => { + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + adminToken = await loginAs('admin', 'supersecret'); + + const projectResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Wildcard Project' }); + projectId = projectResponse.body.id; + + const userResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'julia', password: 'juliapass' }); + userToken = await loginAs('julia', 'juliapass'); + + const policyResponse = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['*'], + notPermissions: [], + }); + const policyId = policyResponse.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId: userResponse.body.id, policyId }); + + const fileResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/files') + .send({ + projectId, + filename: 'wildcard.txt', + storageType: 'local', + storagePath: '/tmp/wildcard.txt', + }); + fileId = fileResponse.body.id; + }); + + test('user with wildcard * policy can read the file', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/files/${fileId}` + ); + expect(response.status).toBe(200); + expect(response.body.id).toBe(fileId); + }); + + test('user with wildcard * policy can delete the file', async () => { + const response = await authenticatedTestClient(userToken).delete( + `/api/v1/files/${fileId}` + ); + expect(response.status).toBe(204); + }); +}); + +// ─── Group 8: Namespace Wildcard files:* ────────────────────────────────── + +describe('Group 8: Policy with files:* grants all file-namespace permissions', () => { + let adminToken: string; + let projectId: string; + let userToken: string; + let fileId: string; + + beforeAll(async () => { + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + adminToken = await loginAs('admin', 'supersecret'); + + const projectResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Namespace Wildcard Project' }); + projectId = projectResponse.body.id; + + const userResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'kevin', password: 'kevinpass' }); + userToken = await loginAs('kevin', 'kevinpass'); + + const policyResponse = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['files:*'], + notPermissions: [], + }); + const policyId = policyResponse.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId: userResponse.body.id, policyId }); + + const fileResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/files') + .send({ + projectId, + filename: 'namespace.txt', + storageType: 'local', + storagePath: '/tmp/namespace.txt', + }); + fileId = fileResponse.body.id; + }); + + test('user with files:* policy can read the file', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/files/${fileId}` + ); + expect(response.status).toBe(200); + expect(response.body.id).toBe(fileId); + }); + + test('user with files:* policy can delete the file', async () => { + const response = await authenticatedTestClient(userToken).delete( + `/api/v1/files/${fileId}` + ); + expect(response.status).toBe(204); + }); +}); + +// ─── Group 9: notPermissions Takes Precedence ───────────────────────────── + +describe('Group 9: notPermissions overrides permissions when action appears in both', () => { + let adminToken: string; + let projectId: string; + let userToken: string; + let fileId: string; + + beforeAll(async () => { + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + adminToken = await loginAs('admin', 'supersecret'); + + const projectResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Conflict Policy Project' }); + projectId = projectResponse.body.id; + + const userResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'luna', password: 'lunapass' }); + userToken = await loginAs('luna', 'lunapass'); + + // Same action appears in both permissions and notPermissions — deny wins + const policyResponse = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['files:GetFile', 'files:DeleteFile'], + notPermissions: ['files:DeleteFile'], + }); + const policyId = policyResponse.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId: userResponse.body.id, policyId }); + + const fileResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/files') + .send({ + projectId, + filename: 'conflict.txt', + storageType: 'local', + storagePath: '/tmp/conflict.txt', + }); + fileId = fileResponse.body.id; + }); + + test('user can read the file (allowed by permissions, not blocked by notPermissions)', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/files/${fileId}` + ); + expect(response.status).toBe(200); + expect(response.body.id).toBe(fileId); + }); + + test('user cannot delete the file even though it is in permissions (notPermissions wins)', async () => { + const response = await authenticatedTestClient(userToken).delete( + `/api/v1/files/${fileId}` + ); + expect(response.status).toBe(403); + expect(response.body.error).toBe('Forbidden'); + }); +}); + +// ─── Group 10: User Without Project Membership ──────────────────────────── + +describe('Group 10: User without project membership is denied access to all file operations', () => { + let adminToken: string; + let projectId: string; + let outsiderToken: string; + let fileId: string; + + beforeAll(async () => { + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + adminToken = await loginAs('admin', 'supersecret'); + + const projectResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Private Project' }); + projectId = projectResponse.body.id; + + // Create outsider user — deliberately NOT added to the project + await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'mark', password: 'markpass' }); + outsiderToken = await loginAs('mark', 'markpass'); + + const fileResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/files') + .send({ + projectId, + filename: 'private.txt', + storageType: 'local', + storagePath: '/tmp/private.txt', + }); + fileId = fileResponse.body.id; + }); + + test('outsider user cannot read the file (no project membership)', async () => { + const response = await authenticatedTestClient(outsiderToken).get( + `/api/v1/files/${fileId}` + ); + expect(response.status).toBe(403); + expect(response.body.error).toBe('Forbidden'); + }); + + test('outsider user cannot delete the file (no project membership)', async () => { + const response = await authenticatedTestClient(outsiderToken).delete( + `/api/v1/files/${fileId}` + ); + expect(response.status).toBe(403); + expect(response.body.error).toBe('Forbidden'); + }); +}); + +// ─── Group 11: Multiple Admins ──────────────────────────────────────────── + +describe('Group 11: Multiple admins can all manage projects and bypass policy checks', () => { + let admin1Token: string; + let admin2Token: string; + let projectId: string; + let fileId: string; + + beforeAll(async () => { + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + admin1Token = await loginAs('admin', 'supersecret'); + + // Admin1 promotes a second user to admin role + await authenticatedTestClient(admin1Token) + .post('/api/v1/users') + .send({ username: 'nina', password: 'ninapass', role: 'admin' }); + admin2Token = await loginAs('nina', 'ninapass'); + + const projectResponse = await authenticatedTestClient(admin1Token) + .post('/api/v1/projects') + .send({ name: 'Admin1 Project' }); + projectId = projectResponse.body.id; + + const fileResponse = await authenticatedTestClient(admin1Token) + .post('/api/v1/files') + .send({ + projectId, + filename: 'admin-file.txt', + storageType: 'local', + storagePath: '/tmp/admin-file.txt', + }); + fileId = fileResponse.body.id; + }); + + test('admin2 can create a project independently', async () => { + const response = await authenticatedTestClient(admin2Token) + .post('/api/v1/projects') + .send({ name: 'Admin2 Project' }); + expect(response.status).toBe(201); + expect(response.body.name).toBe('Admin2 Project'); + }); + + test('admin2 can read files in a project created by admin1', async () => { + const response = await authenticatedTestClient(admin2Token).get( + `/api/v1/files/${fileId}` + ); + expect(response.status).toBe(200); + expect(response.body.id).toBe(fileId); + }); + + test('admin2 can create policies in a project created by admin1', async () => { + const response = await authenticatedTestClient(admin2Token) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['files:GetFile'], + notPermissions: [], + }); + expect(response.status).toBe(201); + expect(response.body.id).toBeDefined(); + }); + + test('admin2 can delete files in a project created by admin1', async () => { + const response = await authenticatedTestClient(admin2Token).delete( + `/api/v1/files/${fileId}` + ); + expect(response.status).toBe(204); + }); +}); + +// ─── Group 12: Multiple Users with Multiple project keys ────────────────────── + +describe('Group 12: Multiple users each with multiple project keys in the same project', () => { + let adminToken: string; + let projectId: string; + let user1ReadKey: string; + let user1DeleteKey: string; + let user2Key: string; + let fileId: string; + + beforeAll(async () => { + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + adminToken = await loginAs('admin', 'supersecret'); + + const projectResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Multi-User project keys Project' }); + projectId = projectResponse.body.id; + + // User1 membership: full permissions + const user1Response = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'oscar', password: 'oscarpass' }); + const user1Token = await loginAs('oscar', 'oscarpass'); + + const fullPolicyResponse = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['files:GetFile', 'files:DeleteFile'], + notPermissions: [], + }); + const fullPolicyId = fullPolicyResponse.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId: user1Response.body.id, policyId: fullPolicyId }); + + // User2 membership: read-only + const user2Response = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'patricia', password: 'patriciapass' }); + const user2Token = await loginAs('patricia', 'patriciapass'); + + const readOnlyPolicyResponse = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['files:GetFile'], + notPermissions: [], + }); + const readOnlyPolicyId = readOnlyPolicyResponse.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId: user2Response.body.id, policyId: readOnlyPolicyId }); + + // project key policies + const readKeyPolicyResponse = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['files:GetFile'], + notPermissions: [], + }); + const readKeyPolicyId = readKeyPolicyResponse.body.id; + + const deleteKeyPolicyResponse = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['files:GetFile', 'files:DeleteFile'], + notPermissions: [], + }); + const deleteKeyPolicyId = deleteKeyPolicyResponse.body.id; + + // User1 creates two project keys: one read-only, one full + const u1ReadKeyResponse = await authenticatedTestClient(user1Token) + .post('/api/v1/project-keys') + .send({ projectId, policyId: readKeyPolicyId, name: 'Oscar Read Key' }); + user1ReadKey = u1ReadKeyResponse.body.key; + + const u1DeleteKeyResponse = await authenticatedTestClient(user1Token) + .post('/api/v1/project-keys') + .send({ + projectId, + policyId: deleteKeyPolicyId, + name: 'Oscar Delete Key', + }); + user1DeleteKey = u1DeleteKeyResponse.body.key; + + // User2 creates a key with full key policy — but membership is read-only, + // so the effective permission (intersection) is still read-only + const u2KeyResponse = await authenticatedTestClient(user2Token) + .post('/api/v1/project-keys') + .send({ + projectId, + policyId: deleteKeyPolicyId, + name: 'Patricia Key', + }); + user2Key = u2KeyResponse.body.key; + + const fileResponse = await authenticatedTestClient(adminToken) + .post('/api/v1/files') + .send({ + projectId, + filename: 'multi.txt', + storageType: 'local', + storagePath: '/tmp/multi.txt', + }); + fileId = fileResponse.body.id; + }); + + test('user1 read key can read the file', async () => { + const response = await testClient + .get(`/api/v1/files/${fileId}`) + .set('Authorization', `Bearer ${user1ReadKey}`); + expect(response.status).toBe(200); + expect(response.body.id).toBe(fileId); + }); + + test('user1 read key cannot delete the file (key policy is read-only)', async () => { + const response = await testClient + .delete(`/api/v1/files/${fileId}`) + .set('Authorization', `Bearer ${user1ReadKey}`); + expect(response.status).toBe(403); + expect(response.body.error).toBe('Forbidden'); + }); + + test('user2 key can read the file', async () => { + const response = await testClient + .get(`/api/v1/files/${fileId}`) + .set('Authorization', `Bearer ${user2Key}`); + expect(response.status).toBe(200); + expect(response.body.id).toBe(fileId); + }); + + test('user2 key cannot delete the file (membership policy is read-only, intersection blocks delete)', async () => { + const response = await testClient + .delete(`/api/v1/files/${fileId}`) + .set('Authorization', `Bearer ${user2Key}`); + expect(response.status).toBe(403); + expect(response.body.error).toBe('Forbidden'); + }); + + test('user1 delete key can read the file', async () => { + const response = await testClient + .get(`/api/v1/files/${fileId}`) + .set('Authorization', `Bearer ${user1DeleteKey}`); + expect(response.status).toBe(200); + expect(response.body.id).toBe(fileId); + }); + + test('user1 delete key can delete the file', async () => { + const response = await testClient + .delete(`/api/v1/files/${fileId}`) + .set('Authorization', `Bearer ${user1DeleteKey}`); + expect(response.status).toBe(204); + }); +}); diff --git a/packages/server/tests/unit/tests/projectKeys.test.ts b/packages/server/tests/unit/tests/projectKeys.test.ts new file mode 100644 index 00000000..6b78dea0 --- /dev/null +++ b/packages/server/tests/unit/tests/projectKeys.test.ts @@ -0,0 +1,243 @@ +import { authenticatedTestClient, loginAs, testClient } from '../testClient'; + +describe('Project Keys', () => { + let adminToken: string; + let aliceToken: string; + let aliceId: string; + let bobToken: string; + let projectId: string; + let policyId: string; + + beforeAll(async () => { + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + adminToken = await loginAs('admin', 'supersecret'); + + const aliceRes = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'alice', password: 'alicepass' }); + aliceId = aliceRes.body.id; + aliceToken = await loginAs('alice', 'alicepass'); + + await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'bob', password: 'bobpass' }); + bobToken = await loginAs('bob', 'bobpass'); + + const projectRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Test Project' }); + projectId = projectRes.body.id; + + const policyRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ permissions: ['files:read'] }); + policyId = policyRes.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId: aliceId, policyId }); + }); + + describe('POST /api/v1/project-keys', () => { + test('returns 401 if not authenticated', async () => { + const response = await testClient + .post('/api/v1/project-keys') + .send({ projectId, policyId, name: 'My Key' }); + + expect(response.status).toBe(401); + }); + + test('returns 400 if required fields are missing', async () => { + const response = await authenticatedTestClient(aliceToken) + .post('/api/v1/project-keys') + .send({ name: 'My Key' }); + + expect(response.status).toBe(400); + }); + + test('returns 400 if project does not exist', async () => { + const response = await authenticatedTestClient(aliceToken) + .post('/api/v1/project-keys') + .send({ projectId: 'proj_nonexistent', policyId, name: 'My Key' }); + + expect(response.status).toBe(400); + }); + + test('returns 403 if user is not a member of the project', async () => { + const response = await authenticatedTestClient(bobToken) + .post('/api/v1/project-keys') + .send({ projectId, policyId, name: 'Bob Key' }); + + expect(response.status).toBe(403); + }); + + test('returns 400 if policy does not belong to the project', async () => { + const otherProjectRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Other Project' }); + const otherProjectId = otherProjectRes.body.id; + + const otherPolicyRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${otherProjectId}/policies`) + .send({ permissions: ['files:read'] }); + const otherPolicyId = otherPolicyRes.body.id; + + const response = await authenticatedTestClient(aliceToken) + .post('/api/v1/project-keys') + .send({ projectId, policyId: otherPolicyId, name: 'My Key' }); + + expect(response.status).toBe(400); + }); + + test('returns 201 and the full key on success', async () => { + const response = await authenticatedTestClient(aliceToken) + .post('/api/v1/project-keys') + .send({ projectId, policyId, name: 'Alice Key' }); + + expect(response.status).toBe(201); + expect(response.body.id).toBeDefined(); + expect(response.body.name).toBe('Alice Key'); + expect(response.body.key).toBeDefined(); + expect(response.body.keyPrefix).toBeDefined(); + expect(response.body.createdAt).toBeDefined(); + expect(response.body.updatedAt).toBeDefined(); + }); + }); + + describe('GET /api/v1/project-keys/:id', () => { + let projectKeyId: string; + + beforeAll(async () => { + const res = await authenticatedTestClient(aliceToken) + .post('/api/v1/project-keys') + .send({ projectId, policyId, name: 'Get Test Key' }); + projectKeyId = res.body.id; + }); + + test('returns 401 if not authenticated', async () => { + const response = await testClient.get( + `/api/v1/project-keys/${projectKeyId}` + ); + + expect(response.status).toBe(401); + }); + + test('returns 404 if project key does not exist', async () => { + const response = await authenticatedTestClient(aliceToken).get( + '/api/v1/project-keys/key_nonexistent' + ); + + expect(response.status).toBe(404); + }); + + test('returns 403 if user does not own the project key', async () => { + const response = await authenticatedTestClient(bobToken).get( + `/api/v1/project-keys/${projectKeyId}` + ); + + expect(response.status).toBe(403); + }); + + test('returns 200 and project key data without the full key', async () => { + const response = await authenticatedTestClient(aliceToken).get( + `/api/v1/project-keys/${projectKeyId}` + ); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(projectKeyId); + expect(response.body.name).toBe('Get Test Key'); + expect(response.body.keyPrefix).toBeDefined(); + expect(response.body.key).toBeUndefined(); + expect(response.body.createdAt).toBeDefined(); + expect(response.body.updatedAt).toBeDefined(); + }); + }); + + describe('PUT /api/v1/project-keys/:id', () => { + let projectKeyId: string; + let newPolicyId: string; + + beforeAll(async () => { + const keyRes = await authenticatedTestClient(aliceToken) + .post('/api/v1/project-keys') + .send({ projectId, policyId, name: 'Update Test Key' }); + projectKeyId = keyRes.body.id; + + const newPolicyRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ permissions: ['files:write'] }); + newPolicyId = newPolicyRes.body.id; + }); + + test('returns 401 if not authenticated', async () => { + const response = await testClient + .put(`/api/v1/project-keys/${projectKeyId}`) + .send({ policyId: newPolicyId }); + + expect(response.status).toBe(401); + }); + + test('returns 400 if policyId is missing', async () => { + const response = await authenticatedTestClient(aliceToken) + .put(`/api/v1/project-keys/${projectKeyId}`) + .send({}); + + expect(response.status).toBe(400); + }); + + test('returns 400 if policy does not exist', async () => { + const response = await authenticatedTestClient(aliceToken) + .put(`/api/v1/project-keys/${projectKeyId}`) + .send({ policyId: 'policy_nonexistent' }); + + expect(response.status).toBe(400); + }); + + test('returns 404 if project key does not exist', async () => { + const response = await authenticatedTestClient(aliceToken) + .put('/api/v1/project-keys/key_nonexistent') + .send({ policyId: newPolicyId }); + + expect(response.status).toBe(404); + }); + + test('returns 403 if user does not own the project key', async () => { + const response = await authenticatedTestClient(bobToken) + .put(`/api/v1/project-keys/${projectKeyId}`) + .send({ policyId: newPolicyId }); + + expect(response.status).toBe(403); + }); + + test('returns 400 if policy belongs to a different project', async () => { + const otherProjectRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Other Update Project' }); + const otherProjectId = otherProjectRes.body.id; + + const otherPolicyRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${otherProjectId}/policies`) + .send({ permissions: ['files:read'] }); + const otherPolicyId = otherPolicyRes.body.id; + + const response = await authenticatedTestClient(aliceToken) + .put(`/api/v1/project-keys/${projectKeyId}`) + .send({ policyId: otherPolicyId }); + + expect(response.status).toBe(400); + }); + + test('returns 200 and the updated project key', async () => { + const response = await authenticatedTestClient(aliceToken) + .put(`/api/v1/project-keys/${projectKeyId}`) + .send({ policyId: newPolicyId }); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(projectKeyId); + expect(response.body.policyId).toBe(newPolicyId); + }); + }); +}); diff --git a/packages/server/tests/unit/tests/projects.test.ts b/packages/server/tests/unit/tests/projects.test.ts new file mode 100644 index 00000000..1885a313 --- /dev/null +++ b/packages/server/tests/unit/tests/projects.test.ts @@ -0,0 +1,486 @@ +import { authenticatedTestClient, loginAs, testClient } from '../testClient'; + +describe('Projects', () => { + let adminToken: string; + let userToken: string; + let userId: string; + + beforeAll(async () => { + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + adminToken = await loginAs('admin', 'supersecret'); + + const createUserRes = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'alice', password: 'alicepass' }); + + userId = createUserRes.body.id; + userToken = await loginAs('alice', 'alicepass'); + }); + + describe('POST /api/v1/projects', () => { + test('admin can create a project', async () => { + const response = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'My Project' }); + + expect(response.status).toBe(201); + expect(response.body.id).toBeDefined(); + expect(response.body.name).toBe('My Project'); + expect(response.body.createdAt).toBeDefined(); + expect(response.body.updatedAt).toBeDefined(); + }); + + test('unauthenticated request cannot create a project', async () => { + const response = await testClient + .post('/api/v1/projects') + .send({ name: 'Unauthorized Project' }); + + expect(response.status).toBe(401); + }); + + test('non-admin user cannot create a project', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/projects') + .send({ name: 'Forbidden Project' }); + + expect(response.status).toBe(403); + }); + }); + + describe('GET /api/v1/projects', () => { + test('admin can list all projects', async () => { + const response = + await authenticatedTestClient(adminToken).get('/api/v1/projects'); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + }); + + test('unauthenticated request cannot list projects', async () => { + const response = await testClient.get('/api/v1/projects'); + + expect(response.status).toBe(401); + }); + + test('user only sees projects they are a member of', async () => { + const projectRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Member Project' }); + const memberProjectId = projectRes.body.id; + + await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Other Project' }); + + const policyRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${memberProjectId}/policies`) + .send({ permissions: ['projects:GetProject'] }); + const policyId = policyRes.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${memberProjectId}/members`) + .send({ userId, policyId }); + + const response = + await authenticatedTestClient(userToken).get('/api/v1/projects'); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + expect( + response.body.some((p: { id: string }) => { + return p.id === memberProjectId; + }) + ).toBe(true); + expect(response.body.length).toBe(1); + }); + describe('project key only sees its scoped project', () => { + let projectAId: string; + let rawProjectKey: string; + + beforeAll(async () => { + const projARes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'project key Project A' }); + projectAId = projARes.body.id; + + const projBRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'project key Project B' }); + const projectBId = projBRes.body.id; + + const policyARes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectAId}/policies`) + .send({ permissions: ['projects:GetProject'] }); + const policyAId = policyARes.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectAId}/members`) + .send({ userId, policyId: policyAId }); + + const policyBRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectBId}/policies`) + .send({ permissions: ['projects:GetProject'] }); + const policyBId = policyBRes.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectBId}/members`) + .send({ userId, policyId: policyBId }); + + const projectKeyRes = await authenticatedTestClient(userToken) + .post('/api/v1/project-keys') + .send({ + projectId: projectAId, + policyId: policyAId, + name: 'Scoped Key', + }); + rawProjectKey = projectKeyRes.body.key; + }); + + test('project key user only sees the scoped project', async () => { + const response = + await authenticatedTestClient(rawProjectKey).get('/api/v1/projects'); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + expect(response.body.length).toBe(1); + expect(response.body[0].id).toBe(projectAId); + }); + }); + }); + + describe('GET /api/v1/projects/:id', () => { + let projectId: string; + + beforeAll(async () => { + const res = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Gettable Project' }); + projectId = res.body.id; + }); + + test('admin can get any project', async () => { + const response = await authenticatedTestClient(adminToken).get( + `/api/v1/projects/${projectId}` + ); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(projectId); + expect(response.body.name).toBe('Gettable Project'); + }); + + test('unauthenticated request cannot get a project', async () => { + const response = await testClient.get(`/api/v1/projects/${projectId}`); + + expect(response.status).toBe(401); + }); + + test('user cannot get a project they are not a member of', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/projects/${projectId}` + ); + + expect(response.status).toBe(403); + }); + + test('user can get a project they are a member of', async () => { + const policyRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ permissions: ['projects:GetProject'] }); + const policyId = policyRes.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId, policyId }); + + const response = await authenticatedTestClient(userToken).get( + `/api/v1/projects/${projectId}` + ); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(projectId); + }); + + test('returns 404 for unknown project id', async () => { + const response = await authenticatedTestClient(adminToken).get( + '/api/v1/projects/proj_nonexistent12345' + ); + + expect(response.status).toBe(404); + }); + }); + + describe('DELETE /api/v1/projects/:id', () => { + test('admin can delete a project', async () => { + const createRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'To Delete' }); + const { id } = createRes.body; + + const deleteRes = await authenticatedTestClient(adminToken).delete( + `/api/v1/projects/${id}` + ); + expect(deleteRes.status).toBe(204); + + const getRes = await authenticatedTestClient(adminToken).get( + `/api/v1/projects/${id}` + ); + expect(getRes.status).toBe(404); + }); + + test('unauthenticated request cannot delete a project', async () => { + const createRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Not Deletable Unauth' }); + const { id } = createRes.body; + + const response = await testClient.delete(`/api/v1/projects/${id}`); + + expect(response.status).toBe(401); + }); + + test('non-admin user cannot delete a project', async () => { + const createRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Not Deletable User' }); + const { id } = createRes.body; + + const response = await authenticatedTestClient(userToken).delete( + `/api/v1/projects/${id}` + ); + + expect(response.status).toBe(403); + }); + + test('returns 404 when deleting non-existent project', async () => { + const response = await authenticatedTestClient(adminToken).delete( + '/api/v1/projects/proj_nonexistent12345' + ); + + expect(response.status).toBe(404); + }); + }); + + describe('POST /api/v1/projects/:projectId/policies', () => { + let projectId: string; + + beforeAll(async () => { + const res = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Policy Project' }); + projectId = res.body.id; + }); + + test('admin can create a project policy', async () => { + const response = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ permissions: ['files:read', 'files:write'] }); + + expect(response.status).toBe(201); + expect(response.body.id).toBeDefined(); + expect(response.body.permissions).toEqual(['files:read', 'files:write']); + expect(response.body.projectId).toBe(projectId); + }); + + test('admin can create a policy with notPermissions', async () => { + const response = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: ['files:read'], + notPermissions: ['files:delete'], + }); + + expect(response.status).toBe(201); + expect(response.body.notPermissions).toEqual(['files:delete']); + }); + + test('unauthenticated request cannot create a policy', async () => { + const response = await testClient + .post(`/api/v1/projects/${projectId}/policies`) + .send({ permissions: ['files:read'] }); + + expect(response.status).toBe(401); + }); + + test('non-admin user cannot create a policy', async () => { + const response = await authenticatedTestClient(userToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ permissions: ['files:read'] }); + + expect(response.status).toBe(403); + }); + + test('returns 404 for non-existent project', async () => { + const response = await authenticatedTestClient(adminToken) + .post('/api/v1/projects/proj_nonexistent12345/policies') + .send({ permissions: ['files:read'] }); + + expect(response.status).toBe(404); + }); + }); + + describe('GET /api/v1/projects/:projectId/policies', () => { + let projectId: string; + let memberUserToken: string; + let memberUserId: string; + + beforeAll(async () => { + const projRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'List Policies Project' }); + projectId = projRes.body.id; + + const userRes = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'policyuser', password: 'policypass' }); + memberUserId = userRes.body.id; + memberUserToken = await loginAs('policyuser', 'policypass'); + + const policyRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ permissions: ['projects:GetProject'] }); + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId: memberUserId, policyId: policyRes.body.id }); + }); + + test('admin can list project policies', async () => { + const response = await authenticatedTestClient(adminToken).get( + `/api/v1/projects/${projectId}/policies` + ); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + expect(response.body.length).toBeGreaterThan(0); + }); + + test('project member can list policies', async () => { + const response = await authenticatedTestClient(memberUserToken).get( + `/api/v1/projects/${projectId}/policies` + ); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + }); + + test('unauthenticated request cannot list policies', async () => { + const response = await testClient.get( + `/api/v1/projects/${projectId}/policies` + ); + + expect(response.status).toBe(401); + }); + + test('non-member user cannot list policies', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/projects/${projectId}/policies` + ); + + expect(response.status).toBe(403); + }); + }); + + describe('POST /api/v1/projects/:projectId/members', () => { + let projectId: string; + let policyId: string; + + beforeAll(async () => { + const projRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Members Project' }); + projectId = projRes.body.id; + + const policyRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ permissions: ['projects:GetProject'] }); + policyId = policyRes.body.id; + }); + + test('admin can add a user to a project', async () => { + const response = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId, policyId }); + + expect(response.status).toBe(201); + }); + + test('unauthenticated request cannot add a member', async () => { + const response = await testClient + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId, policyId }); + + expect(response.status).toBe(401); + }); + + test('non-admin user cannot add a member', async () => { + const response = await authenticatedTestClient(userToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId, policyId }); + + expect(response.status).toBe(403); + }); + + test('returns 404 for non-existent project', async () => { + const response = await authenticatedTestClient(adminToken) + .post('/api/v1/projects/proj_nonexistent12345/members') + .send({ userId, policyId }); + + expect(response.status).toBe(404); + }); + }); + + describe('cascade deletion when a project is deleted', () => { + test('deleting a project removes its policies, memberships, and project keys', async () => { + const projRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Cascade Test Project' }); + expect(projRes.status).toBe(201); + const projectId = projRes.body.id; + + const policyRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ permissions: ['projects:GetProject'] }); + expect(policyRes.status).toBe(201); + const policyId = policyRes.body.id; + + const memberRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId, policyId }); + expect(memberRes.status).toBe(201); + + const projectKeyRes = await authenticatedTestClient(userToken) + .post('/api/v1/project-keys') + .send({ projectId, policyId, name: 'Cascade Test Key' }); + expect(projectKeyRes.status).toBe(201); + const projectKeyId = projectKeyRes.body.id; + + const deleteRes = await authenticatedTestClient(adminToken).delete( + `/api/v1/projects/${projectId}` + ); + expect(deleteRes.status).toBe(204); + + // Policies cascade-deleted: project no longer found, so list returns [] + const policiesRes = await authenticatedTestClient(adminToken).get( + `/api/v1/projects/${projectId}/policies` + ); + expect(policiesRes.body).toEqual([]); + + // UserProject cascade-deleted: deleted project no longer in alice's project list + const projectsRes = + await authenticatedTestClient(userToken).get('/api/v1/projects'); + const projectIds = projectsRes.body.map((p: { id: string }) => { + return p.id; + }); + expect(projectIds).not.toContain(projectId); + + // ProjectKey cascade-deleted: key no longer found + const projectKeyGetRes = await authenticatedTestClient(userToken).get( + `/api/v1/project-keys/${projectKeyId}` + ); + expect(projectKeyGetRes.status).toBe(404); + }); + }); +}); diff --git a/packages/server/tests/unit/tests/rest/documents.test.ts b/packages/server/tests/unit/tests/rest/documents.test.ts deleted file mode 100644 index 1cc8ef6a..00000000 --- a/packages/server/tests/unit/tests/rest/documents.test.ts +++ /dev/null @@ -1,463 +0,0 @@ -import { - createDocument, - deleteDocument, - type DocumentRecord, - getDocument, - listDocuments, - searchDocumentsBySimilarity, - updateDocument, -} from '@soat/documents-core'; -import { getConfigFromEnv } from '@soat/embeddings-core'; -import { app } from 'src/app'; -import request from 'supertest'; - -describe('Documents API', () => { - beforeEach(() => { - jest.clearAllMocks(); - jest.mocked(getConfigFromEnv).mockReset(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - jest.mocked(getConfigFromEnv).mockReturnValue(undefined as any); - }); - - describe('GET /api/v1/documents/', () => { - test('should list documents successfully', async () => { - const mockDocuments = [ - { - id: 'doc-1', - title: 'Test Document 1', - fileId: 'file-1', - embeddingModel: 'test-model', - embeddingProvider: 'test-provider', - metadata: {}, - createdAt: new Date('2026-01-09T16:27:02.008Z'), - updatedAt: new Date('2026-01-09T16:27:02.008Z'), - }, - ]; - - jest - .mocked(listDocuments) - .mockResolvedValue(mockDocuments as DocumentRecord[]); - - const response = await request(app.callback()) - .get('/api/v1/documents/') - .set('Accept', 'application/json'); - - expect(response.status).toBe(200); - expect(response.body).toEqual({ - success: true, - documents: [ - { - id: 'doc-1', - title: 'Test Document 1', - fileId: 'file-1', - embeddingModel: 'test-model', - embeddingProvider: 'test-provider', - metadata: {}, - createdAt: '2026-01-09T16:27:02.008Z', - updatedAt: '2026-01-09T16:27:02.008Z', - }, - ], - }); - expect(listDocuments).toHaveBeenCalled(); - }); - - test('should handle error when listing documents', async () => { - jest.mocked(listDocuments).mockRejectedValue(new Error('Database error')); - - const response = await request(app.callback()) - .get('/api/v1/documents/') - .set('Accept', 'application/json'); - - expect(response.status).toBe(500); - expect(response.body).toEqual({ - success: false, - error: 'Database error', - }); - }); - }); - - describe('POST /api/v1/documents/', () => { - test('should create document successfully', async () => { - const mockDocument = { - id: 'doc-1', - title: 'Test Document', - fileId: 'file-1', - content: Buffer.from('Test content'), - embedding: [0.1, 0.2, 0.3], - embeddingModel: 'test-model', - embeddingProvider: 'test-provider', - metadata: { key: 'value' }, - createdAt: new Date(), - updatedAt: new Date(), - }; - - jest.mocked(createDocument).mockResolvedValue(mockDocument); - - const response = await request(app.callback()) - .post('/api/v1/documents/') - .send({ - content: 'Test content', - title: 'Test Document', - metadata: { key: 'value' }, - generateEmbedding: true, - }) - .set('Accept', 'application/json'); - - expect(response.status).toBe(201); - expect(response.body).toEqual({ - success: true, - document: { - id: 'doc-1', - title: 'Test Document', - fileId: 'file-1', - embeddingModel: 'test-model', - embeddingProvider: 'test-provider', - hasEmbedding: true, - metadata: { key: 'value' }, - createdAt: mockDocument.createdAt.toISOString(), - updatedAt: mockDocument.updatedAt.toISOString(), - }, - }); - expect(createDocument).toHaveBeenCalledWith({ - storageConfig: { type: 'local', local: { path: '/tmp/documents' } }, - embeddingConfig: undefined, // Assuming no env config in tests - content: 'Test content', - options: { - title: 'Test Document', - metadata: { key: 'value' }, - generateEmbedding: true, - }, - }); - }); - - test('should return 400 if content is missing', async () => { - const response = await request(app.callback()) - .post('/api/v1/documents/') - .send({ - title: 'Test Document', - }) - .set('Accept', 'application/json'); - - expect(response.status).toBe(400); - expect(response.body).toEqual({ - success: false, - error: 'Content is required', - }); - }); - - test('should handle error when creating document', async () => { - jest - .mocked(createDocument) - .mockRejectedValue(new Error('Creation error')); - - const response = await request(app.callback()) - .post('/api/v1/documents/') - .send({ - content: 'Test content', - }) - .set('Accept', 'application/json'); - - expect(response.status).toBe(500); - expect(response.body).toEqual({ - success: false, - error: 'Creation error', - }); - }); - }); - - describe('GET /api/v1/documents/search', () => { - test('should search documents successfully', async () => { - const mockDocuments = [ - { - id: 'doc-1', - title: 'Test Document', - fileId: 'file-1', - content: Buffer.from('Test content'), - embeddingModel: 'test-model', - embeddingProvider: 'test-provider', - metadata: {}, - createdAt: new Date(), - updatedAt: new Date(), - }, - ]; - - jest.mocked(getConfigFromEnv).mockReturnValue({ - provider: 'ollama', - ollama: { model: 'test', host: 'localhost' }, - }); - jest.mocked(searchDocumentsBySimilarity).mockResolvedValue(mockDocuments); - - const response = await request(app.callback()) - .get('/api/v1/documents/search?query=test') - .set('Accept', 'application/json'); - - expect(response.status).toBe(200); - expect(response.body).toEqual({ - success: true, - documents: [ - { - id: 'doc-1', - title: 'Test Document', - fileId: 'file-1', - content: 'Test content', - embeddingModel: 'test-model', - embeddingProvider: 'test-provider', - metadata: {}, - createdAt: mockDocuments[0].createdAt.toISOString(), - updatedAt: mockDocuments[0].updatedAt.toISOString(), - }, - ], - }); - expect(searchDocumentsBySimilarity).toHaveBeenCalledWith({ - storageConfig: { type: 'local', local: { path: '/tmp/documents' } }, - embeddingConfig: { - provider: 'ollama', - ollama: { model: 'test', host: 'localhost' }, - }, - query: 'test', - options: {}, - }); - }); - - test('should return 400 if query is missing', async () => { - const response = await request(app.callback()) - .get('/api/v1/documents/search') - .set('Accept', 'application/json'); - - expect(response.status).toBe(400); - expect(response.body).toEqual({ - success: false, - error: 'Query is required', - }); - }); - - test('should handle error when searching documents', async () => { - jest.mocked(getConfigFromEnv).mockReturnValue({ - provider: 'ollama', - ollama: { model: 'test', host: 'localhost' }, - }); - jest - .mocked(searchDocumentsBySimilarity) - .mockRejectedValue(new Error('Search error')); - - const response = await request(app.callback()) - .get('/api/v1/documents/search?query=test') - .set('Accept', 'application/json'); - - expect(response.status).toBe(500); - expect(response.body).toEqual({ - success: false, - error: 'Search error', - }); - }); - }); - - describe('GET /api/v1/documents/:id', () => { - test('should get document by id successfully', async () => { - const mockDocument = { - id: 'doc-1', - title: 'Test Document', - fileId: 'file-1', - content: Buffer.from('Test content'), - embedding: [0.1, 0.2, 0.3], - embeddingModel: 'test-model', - embeddingProvider: 'test-provider', - metadata: {}, - createdAt: new Date(), - updatedAt: new Date(), - }; - - jest.mocked(getDocument).mockResolvedValue(mockDocument); - - const response = await request(app.callback()) - .get('/api/v1/documents/doc-1') - .set('Accept', 'application/json'); - - expect(response.status).toBe(200); - expect(response.body).toEqual({ - success: true, - document: { - id: 'doc-1', - title: 'Test Document', - fileId: 'file-1', - content: 'Test content', - embeddingModel: 'test-model', - embeddingProvider: 'test-provider', - hasEmbedding: true, - metadata: {}, - createdAt: mockDocument.createdAt.toISOString(), - updatedAt: mockDocument.updatedAt.toISOString(), - }, - }); - expect(getDocument).toHaveBeenCalledWith({ - storageConfig: { type: 'local', local: { path: '/tmp/documents' } }, - id: 'doc-1', - }); - }); - - test('should return 404 if document not found', async () => { - jest.mocked(getDocument).mockResolvedValue(null); - - const response = await request(app.callback()) - .get('/api/v1/documents/doc-1') - .set('Accept', 'application/json'); - - expect(response.status).toBe(404); - expect(response.body).toEqual({ - success: false, - error: 'Document not found', - }); - }); - - test('should handle error when getting document', async () => { - jest.mocked(getDocument).mockRejectedValue(new Error('Get error')); - - const response = await request(app.callback()) - .get('/api/v1/documents/doc-1') - .set('Accept', 'application/json'); - - expect(response.status).toBe(500); - expect(response.body).toEqual({ - success: false, - error: 'Get error', - }); - }); - }); - - describe('PUT /api/v1/documents/:id', () => { - test('should update document successfully', async () => { - const mockDocument = { - id: 'doc-1', - title: 'Updated Document', - fileId: 'file-1', - content: Buffer.from('Updated content'), - embedding: [0.1, 0.2, 0.3], - embeddingModel: 'test-model', - embeddingProvider: 'test-provider', - metadata: { updated: true }, - createdAt: new Date(), - updatedAt: new Date(), - }; - - jest.mocked(updateDocument).mockResolvedValue(mockDocument); - - const response = await request(app.callback()) - .put('/api/v1/documents/doc-1') - .send({ - content: 'Updated content', - title: 'Updated Document', - metadata: { updated: true }, - regenerateEmbedding: true, - }) - .set('Accept', 'application/json'); - - expect(response.status).toBe(200); - expect(response.body).toEqual({ - success: true, - document: { - id: 'doc-1', - title: 'Updated Document', - fileId: 'file-1', - content: 'Updated content', - embeddingModel: 'test-model', - embeddingProvider: 'test-provider', - hasEmbedding: true, - metadata: { updated: true }, - createdAt: mockDocument.createdAt.toISOString(), - updatedAt: mockDocument.updatedAt.toISOString(), - }, - }); - expect(updateDocument).toHaveBeenCalledWith( - expect.objectContaining({ - storageConfig: { type: 'local', local: { path: '/tmp/documents' } }, - id: 'doc-1', - content: 'Updated content', - title: 'Updated Document', - metadata: { updated: true }, - regenerateEmbedding: true, - }) - ); - }); - - test('should return 404 if document not found', async () => { - jest.mocked(updateDocument).mockResolvedValue(null); - - const response = await request(app.callback()) - .put('/api/v1/documents/doc-1') - .send({ - content: 'Updated content', - }) - .set('Accept', 'application/json'); - - expect(response.status).toBe(404); - expect(response.body).toEqual({ - success: false, - error: 'Document not found', - }); - }); - - test('should handle error when updating document', async () => { - jest.mocked(updateDocument).mockRejectedValue(new Error('Update error')); - - const response = await request(app.callback()) - .put('/api/v1/documents/doc-1') - .send({ - content: 'Updated content', - }) - .set('Accept', 'application/json'); - - expect(response.status).toBe(500); - expect(response.body).toEqual({ - success: false, - error: 'Update error', - }); - }); - }); - - describe('DELETE /api/v1/documents/:id', () => { - test('should delete document successfully', async () => { - jest.mocked(deleteDocument).mockResolvedValue(true); - - const response = await request(app.callback()) - .delete('/api/v1/documents/doc-1') - .set('Accept', 'application/json'); - - expect(response.status).toBe(200); - expect(response.body).toEqual({ - success: true, - }); - expect(deleteDocument).toHaveBeenCalledWith({ - storageConfig: { type: 'local', local: { path: '/tmp/documents' } }, - id: 'doc-1', - }); - }); - - test('should return 404 if document not found', async () => { - jest.mocked(deleteDocument).mockResolvedValue(false); - - const response = await request(app.callback()) - .delete('/api/v1/documents/doc-1') - .set('Accept', 'application/json'); - - expect(response.status).toBe(404); - expect(response.body).toEqual({ - success: false, - error: 'Document not found', - }); - }); - - test('should handle error when deleting document', async () => { - jest.mocked(deleteDocument).mockRejectedValue(new Error('Delete error')); - - const response = await request(app.callback()) - .delete('/api/v1/documents/doc-1') - .set('Accept', 'application/json'); - - expect(response.status).toBe(500); - expect(response.body).toEqual({ - success: false, - error: 'Delete error', - }); - }); - }); -}); diff --git a/packages/server/tests/unit/tests/rest/files.test.ts b/packages/server/tests/unit/tests/rest/files.test.ts deleted file mode 100644 index 96687b0b..00000000 --- a/packages/server/tests/unit/tests/rest/files.test.ts +++ /dev/null @@ -1,248 +0,0 @@ -import { - deleteFile, - type FileData, - type FileRecord, - getFileRecord, - listFileRecords, - retrieveFileById, - saveFile, -} from '@soat/files-core'; -import { app } from 'src/app'; -import request from 'supertest'; - -describe('Files API', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('GET /api/v1/files/', () => { - test('should list files successfully', async () => { - const mockFiles = [ - { - id: 'file-1', - filename: 'test1.txt', - contentType: 'text/plain', - size: 100, - storageType: 'local', - storagePath: '/tmp/files/test1.txt', - metadata: {}, - createdAt: '2026-01-09T16:27:01.999Z', - updatedAt: '2026-01-09T16:27:01.999Z', - }, - ]; - - jest - .mocked(listFileRecords) - .mockResolvedValue(mockFiles as unknown as FileRecord[]); - - const response = await request(app.callback()) - .get('/api/v1/files/') - .set('Accept', 'application/json'); - - expect(response.status).toBe(200); - expect(response.body).toEqual({ - success: true, - files: mockFiles, - }); - expect(listFileRecords).toHaveBeenCalled(); - }); - - test('should handle error when listing files', async () => { - jest - .mocked(listFileRecords) - .mockRejectedValue(new Error('Database error')); - - const response = await request(app.callback()) - .get('/api/v1/files/') - .set('Accept', 'application/json'); - - expect(response.status).toBe(500); - expect(response.body).toEqual({ - success: false, - error: 'Database error', - }); - }); - }); - - describe('POST /api/v1/files/upload', () => { - test('should create a file via REST API', async () => { - const savedFile = { - id: 'test-id', - filename: 'test.txt', - content: 'Hello, World!', - metadata: {}, - }; - - jest.mocked(saveFile).mockResolvedValue(savedFile); - - const response = await request(app.callback()) - .post('/api/v1/files/upload') - .send({ - content: 'Hello, World!', - options: { metadata: { filename: 'test.txt' } }, - }) - .set('Accept', 'application/json'); - - expect(response.status).toBe(201); - expect(response.body).toEqual({ - id: 'test-id', - filename: 'test.txt', - success: true, - }); - expect(saveFile).toHaveBeenCalledWith({ - config: { local: { path: '/tmp/files' }, type: 'local' }, - content: 'Hello, World!', - options: { metadata: { filename: 'test.txt' } }, - }); - }); - - test('should return 400 if content is missing', async () => { - const response = await request(app.callback()) - .post('/api/v1/files/upload') - .send({ - options: { metadata: { filename: 'test.txt' } }, - }) - .set('Accept', 'application/json'); - - expect(response.status).toBe(400); - expect(response.body).toEqual({ - success: false, - error: 'Content is required', - }); - }); - - test('should handle error when uploading file', async () => { - jest.mocked(saveFile).mockRejectedValue(new Error('Upload error')); - - const response = await request(app.callback()) - .post('/api/v1/files/upload') - .send({ - content: 'Hello, World!', - options: { metadata: { filename: 'test.txt' } }, - }) - .set('Accept', 'application/json'); - - expect(response.status).toBe(500); - expect(response.body).toEqual({ - success: false, - error: 'Upload error', - }); - }); - }); - - describe('GET /api/v1/files/:id', () => { - test('should get file by id successfully', async () => { - const mockFile = { - id: 'file-1', - content: 'Hello, World!', - }; - const mockRecord = { - id: 'file-1', - filename: 'test.txt', - contentType: 'text/plain', - size: 13, - storageType: 'local', - storagePath: '/tmp/files/test.txt', - metadata: { filename: 'test.txt' }, - createdAt: '2026-01-09T16:27:02.079Z', - updatedAt: '2026-01-09T16:27:02.079Z', - }; - - jest.mocked(retrieveFileById).mockResolvedValue(mockFile as FileData); - jest - .mocked(getFileRecord) - .mockResolvedValue(mockRecord as unknown as FileRecord); - - const response = await request(app.callback()) - .get('/api/v1/files/file-1') - .set('Accept', 'application/json'); - - expect(response.status).toBe(200); - expect(response.body).toEqual({ - success: true, - file: mockFile, - record: mockRecord, - }); - expect(retrieveFileById).toHaveBeenCalledWith({ - config: { local: { path: '/tmp/files' }, type: 'local' }, - id: 'file-1', - }); - expect(getFileRecord).toHaveBeenCalledWith('file-1'); - }); - - test('should return 404 if file not found', async () => { - jest.mocked(retrieveFileById).mockResolvedValue(null); - - const response = await request(app.callback()) - .get('/api/v1/files/file-1') - .set('Accept', 'application/json'); - - expect(response.status).toBe(404); - expect(response.body).toEqual({ - success: false, - error: 'File not found', - }); - }); - - test('should handle error when getting file', async () => { - jest.mocked(retrieveFileById).mockRejectedValue(new Error('Get error')); - - const response = await request(app.callback()) - .get('/api/v1/files/file-1') - .set('Accept', 'application/json'); - - expect(response.status).toBe(500); - expect(response.body).toEqual({ - success: false, - error: 'Get error', - }); - }); - }); - - describe('DELETE /api/v1/files/:id', () => { - test('should delete file successfully', async () => { - jest.mocked(deleteFile).mockResolvedValue(true); - - const response = await request(app.callback()) - .delete('/api/v1/files/file-1') - .set('Accept', 'application/json'); - - expect(response.status).toBe(200); - expect(response.body).toEqual({ - success: true, - }); - expect(deleteFile).toHaveBeenCalledWith({ - config: { local: { path: '/tmp/files' }, type: 'local' }, - id: 'file-1', - }); - }); - - test('should return 404 if file not found', async () => { - jest.mocked(deleteFile).mockResolvedValue(false); - - const response = await request(app.callback()) - .delete('/api/v1/files/file-1') - .set('Accept', 'application/json'); - - expect(response.status).toBe(404); - expect(response.body).toEqual({ - success: false, - error: 'File not found', - }); - }); - - test('should handle error when deleting file', async () => { - jest.mocked(deleteFile).mockRejectedValue(new Error('Delete error')); - - const response = await request(app.callback()) - .delete('/api/v1/files/file-1') - .set('Accept', 'application/json'); - - expect(response.status).toBe(500); - expect(response.body).toEqual({ - success: false, - error: 'Delete error', - }); - }); - }); -}); diff --git a/packages/server/tests/unit/tests/secrets.test.ts b/packages/server/tests/unit/tests/secrets.test.ts new file mode 100644 index 00000000..c4e8eb13 --- /dev/null +++ b/packages/server/tests/unit/tests/secrets.test.ts @@ -0,0 +1,300 @@ +import { authenticatedTestClient, loginAs, testClient } from '../testClient'; + +describe('Secrets', () => { + let adminToken: string; + let userToken: string; + let userId: string; + let projectId: string; + let otherProjectId: string; + let policyId: string; + + beforeAll(async () => { + // eslint-disable-next-line turbo/no-undeclared-env-vars + process.env.SECRETS_ENCRYPTION_KEY = '0'.repeat(64); + + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'secretsadmin', password: 'supersecret' }); + + adminToken = await loginAs('secretsadmin', 'supersecret'); + + const createUserRes = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'secretsuser', password: 'secretspass' }); + + userId = createUserRes.body.id; + userToken = await loginAs('secretsuser', 'secretspass'); + + const projectRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Secrets Test Project' }); + projectId = projectRes.body.id; + + const otherProjectRes = await authenticatedTestClient(adminToken) + .post('/api/v1/projects') + .send({ name: 'Secrets Other Project' }); + otherProjectId = otherProjectRes.body.id; + + const policyRes = await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/policies`) + .send({ + permissions: [ + 'secrets:ListSecrets', + 'secrets:GetSecret', + 'secrets:CreateSecret', + 'secrets:UpdateSecret', + 'secrets:DeleteSecret', + ], + }); + policyId = policyRes.body.id; + + await authenticatedTestClient(adminToken) + .post(`/api/v1/projects/${projectId}/members`) + .send({ userId, policyId }); + }); + + describe('GET /api/v1/secrets', () => { + test('authenticated user can list secrets', async () => { + const response = await authenticatedTestClient(userToken) + .get('/api/v1/secrets') + .query({ projectId }); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient.get('/api/v1/secrets'); + expect(response.status).toBe(401); + }); + + test('user without access to project returns 403', async () => { + const response = await authenticatedTestClient(userToken) + .get('/api/v1/secrets') + .query({ projectId: otherProjectId }); + + expect(response.status).toBe(403); + }); + }); + + describe('POST /api/v1/secrets', () => { + test('authenticated user with permission can create a secret', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/secrets') + .send({ projectId, name: 'Test Secret', value: 'supersecretvalue' }); + + expect(response.status).toBe(201); + expect(response.body.id).toBeDefined(); + expect(response.body.name).toBe('Test Secret'); + expect(response.body.projectId).toBe(projectId); + expect(response.body.hasValue).toBe(true); + // value must never be returned + expect(response.body.value).toBeUndefined(); + }); + + test('create without name returns 400', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/secrets') + .send({ projectId }); + + expect(response.status).toBe(400); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient + .post('/api/v1/secrets') + .send({ projectId, name: 'Test' }); + + expect(response.status).toBe(401); + }); + + test('user without permission on project returns 403', async () => { + const response = await authenticatedTestClient(userToken) + .post('/api/v1/secrets') + .send({ projectId: otherProjectId, name: 'Test' }); + + expect(response.status).toBe(403); + }); + }); + + describe('GET /api/v1/secrets/:secretId', () => { + let secretId: string; + + beforeAll(async () => { + const res = await authenticatedTestClient(adminToken) + .post('/api/v1/secrets') + .send({ projectId, name: 'Get Test Secret' }); + secretId = res.body.id; + }); + + test('authenticated user with permission can get a secret', async () => { + const response = await authenticatedTestClient(userToken).get( + `/api/v1/secrets/${secretId}` + ); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(secretId); + expect(response.body.projectId).toBe(projectId); + expect(response.body.value).toBeUndefined(); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient.get(`/api/v1/secrets/${secretId}`); + expect(response.status).toBe(401); + }); + + test('user without permission returns 403', async () => { + // Create a secret in otherProject (as admin) and try to access it as user + const adminRes = await authenticatedTestClient(adminToken) + .post('/api/v1/secrets') + .send({ projectId: otherProjectId, name: 'Other Secret' }); + const otherId = adminRes.body.id; + + const response = await authenticatedTestClient(userToken).get( + `/api/v1/secrets/${otherId}` + ); + expect(response.status).toBe(403); + }); + + test('unknown ID returns 404', async () => { + const response = await authenticatedTestClient(userToken).get( + '/api/v1/secrets/sec_doesnotexist' + ); + expect(response.status).toBe(404); + }); + }); + + describe('PATCH /api/v1/secrets/:secretId', () => { + let secretId: string; + + beforeAll(async () => { + const res = await authenticatedTestClient(adminToken) + .post('/api/v1/secrets') + .send({ projectId, name: 'Patch Test Secret' }); + secretId = res.body.id; + }); + + test('authenticated user with permission can update a secret', async () => { + const response = await authenticatedTestClient(userToken) + .patch(`/api/v1/secrets/${secretId}`) + .send({ name: 'Updated Name', value: 'newvalue' }); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(secretId); + expect(response.body.name).toBe('Updated Name'); + expect(response.body.hasValue).toBe(true); + expect(response.body.value).toBeUndefined(); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient + .patch(`/api/v1/secrets/${secretId}`) + .send({ name: 'x' }); + expect(response.status).toBe(401); + }); + + test('user without permission returns 403', async () => { + const adminRes = await authenticatedTestClient(adminToken) + .post('/api/v1/secrets') + .send({ projectId: otherProjectId, name: 'Other Patch Secret' }); + + const response = await authenticatedTestClient(userToken) + .patch(`/api/v1/secrets/${adminRes.body.id}`) + .send({ name: 'x' }); + expect(response.status).toBe(403); + }); + + test('unknown ID returns 404', async () => { + const response = await authenticatedTestClient(userToken) + .patch('/api/v1/secrets/sec_doesnotexist') + .send({ name: 'x' }); + expect(response.status).toBe(404); + }); + }); + + describe('DELETE /api/v1/secrets/:secretId', () => { + test('authenticated user with permission can delete a secret', async () => { + const createRes = await authenticatedTestClient(adminToken) + .post('/api/v1/secrets') + .send({ projectId, name: 'To Delete' }); + const secretId = createRes.body.id; + + const response = await authenticatedTestClient(userToken).delete( + `/api/v1/secrets/${secretId}` + ); + expect(response.status).toBe(204); + }); + + test('unauthenticated request returns 401', async () => { + const response = await testClient.delete( + '/api/v1/secrets/sec_doesnotexist' + ); + expect(response.status).toBe(401); + }); + + test('user without permission returns 403', async () => { + const adminRes = await authenticatedTestClient(adminToken) + .post('/api/v1/secrets') + .send({ projectId: otherProjectId, name: 'Other Delete Secret' }); + + const response = await authenticatedTestClient(userToken).delete( + `/api/v1/secrets/${adminRes.body.id}` + ); + expect(response.status).toBe(403); + }); + + test('unknown ID returns 404', async () => { + const response = await authenticatedTestClient(userToken).delete( + '/api/v1/secrets/sec_doesnotexist' + ); + expect(response.status).toBe(404); + }); + + test('secret referenced by AI provider returns 409 without force', async () => { + // eslint-disable-next-line turbo/no-undeclared-env-vars + process.env.SECRETS_ENCRYPTION_KEY = '0'.repeat(64); + + const secretRes = await authenticatedTestClient(adminToken) + .post('/api/v1/secrets') + .send({ projectId, name: 'Linked Secret' }); + const linkedSecretId = secretRes.body.id; + + await authenticatedTestClient(adminToken) + .post('/api/v1/ai-providers') + .send({ + projectId, + secretId: linkedSecretId, + name: 'Test Provider', + provider: 'openai', + defaultModel: 'gpt-4o', + }); + + const response = await authenticatedTestClient(userToken).delete( + `/api/v1/secrets/${linkedSecretId}` + ); + expect(response.status).toBe(409); + }); + + test('secret referenced by AI provider deleted with force=true returns 204', async () => { + const secretRes = await authenticatedTestClient(adminToken) + .post('/api/v1/secrets') + .send({ projectId, name: 'Force Delete Secret' }); + const linkedSecretId = secretRes.body.id; + + await authenticatedTestClient(adminToken) + .post('/api/v1/ai-providers') + .send({ + projectId, + secretId: linkedSecretId, + name: 'Test Provider Force', + provider: 'openai', + defaultModel: 'gpt-4o', + }); + + const response = await authenticatedTestClient(userToken).delete( + `/api/v1/secrets/${linkedSecretId}?force=true` + ); + expect(response.status).toBe(204); + }); + }); +}); diff --git a/packages/server/tests/unit/tests/users.test.ts b/packages/server/tests/unit/tests/users.test.ts new file mode 100644 index 00000000..aa23c91c --- /dev/null +++ b/packages/server/tests/unit/tests/users.test.ts @@ -0,0 +1,248 @@ +import { authenticatedTestClient, loginAs, testClient } from '../testClient'; + +describe('POST /api/v1/users/bootstrap', () => { + test('should create the first admin user and return 201', async () => { + const response = await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + expect(response.status).toBe(201); + expect(response.body.id).toBeDefined(); + expect(response.body.username).toBe('admin'); + expect(response.body.role).toBe('admin'); + expect(response.body.password).toBeUndefined(); + }); + + test('should return 409 if a user already exists', async () => { + await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin', password: 'supersecret' }); + + const response = await testClient + .post('/api/v1/users/bootstrap') + .send({ username: 'admin2', password: 'anotherpassword' }); + + expect(response.status).toBe(409); + }); +}); + +describe('POST /api/v1/users/login', () => { + test('should return token and user data on valid credentials', async () => { + const response = await testClient + .post('/api/v1/users/login') + .send({ username: 'admin', password: 'supersecret' }); + + expect(response.status).toBe(200); + expect(response.body.token).toBeDefined(); + expect(response.body.username).toBe('admin'); + expect(response.body.role).toBe('admin'); + expect(response.body.password).toBeUndefined(); + }); + + test('should return 401 on invalid credentials', async () => { + const response = await testClient + .post('/api/v1/users/login') + .send({ username: 'admin', password: 'wrongpassword' }); + + expect(response.status).toBe(401); + }); +}); + +describe('Admin user operations', () => { + let adminToken: string; + + beforeAll(async () => { + adminToken = await loginAs('admin', 'supersecret'); + }); + + describe('POST /api/v1/users', () => { + test('admin can create a regular user', async () => { + const response = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'alice', password: 'alicepass', role: 'user' }); + + expect(response.status).toBe(201); + expect(response.body.id).toBeDefined(); + expect(response.body.username).toBe('alice'); + expect(response.body.role).toBe('user'); + expect(response.body.password).toBeUndefined(); + }); + + test('admin can create another admin', async () => { + const response = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'admin2', password: 'admin2pass', role: 'admin' }); + + expect(response.status).toBe(201); + expect(response.body.username).toBe('admin2'); + expect(response.body.role).toBe('admin'); + }); + + test('unauthenticated request cannot create a user', async () => { + const response = await testClient + .post('/api/v1/users') + .send({ username: 'hacker', password: 'pass' }); + + expect(response.status).toBe(401); + }); + + test('non-admin user cannot create a user', async () => { + const aliceToken = await loginAs('alice', 'alicepass'); + const response = await authenticatedTestClient(aliceToken) + .post('/api/v1/users') + .send({ username: 'bob', password: 'bobpass' }); + + expect(response.status).toBe(403); + }); + }); + + describe('GET /api/v1/users', () => { + test('admin can list users', async () => { + const response = + await authenticatedTestClient(adminToken).get('/api/v1/users'); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + expect( + response.body.some((u: { username: string }) => { + return u.username === 'admin'; + }) + ).toBe(true); + }); + + test('unauthenticated request cannot list users', async () => { + const response = await testClient.get('/api/v1/users'); + + expect(response.status).toBe(401); + }); + + test('non-admin user cannot list users', async () => { + const aliceToken = await loginAs('alice', 'alicepass'); + const response = + await authenticatedTestClient(aliceToken).get('/api/v1/users'); + + expect(response.status).toBe(403); + }); + + test('second admin can also list users', async () => { + const admin2Token = await loginAs('admin2', 'admin2pass'); + const response = + await authenticatedTestClient(admin2Token).get('/api/v1/users'); + + expect(response.status).toBe(200); + expect(Array.isArray(response.body)).toBe(true); + }); + }); + + describe('GET /api/v1/users/:id', () => { + test('admin can get a user by id', async () => { + const listRes = + await authenticatedTestClient(adminToken).get('/api/v1/users'); + const alice = listRes.body.find((u: { username: string }) => { + return u.username === 'alice'; + }); + + const response = await authenticatedTestClient(adminToken).get( + `/api/v1/users/${alice.id}` + ); + + expect(response.status).toBe(200); + expect(response.body.username).toBe('alice'); + }); + + test('should return 404 for unknown user id', async () => { + const response = await authenticatedTestClient(adminToken).get( + '/api/v1/users/usr_nonexistent12345' + ); + + expect(response.status).toBe(404); + }); + + test('unauthenticated request cannot get a user', async () => { + const listRes = + await authenticatedTestClient(adminToken).get('/api/v1/users'); + const alice = listRes.body.find((u: { username: string }) => { + return u.username === 'alice'; + }); + + const response = await testClient.get(`/api/v1/users/${alice.id}`); + + expect(response.status).toBe(401); + }); + + test('non-admin user cannot get another user', async () => { + const aliceToken = await loginAs('alice', 'alicepass'); + const listRes = + await authenticatedTestClient(adminToken).get('/api/v1/users'); + const admin = listRes.body.find((u: { username: string }) => { + return u.username === 'admin'; + }); + + const response = await authenticatedTestClient(aliceToken).get( + `/api/v1/users/${admin.id}` + ); + + expect(response.status).toBe(403); + }); + }); + + describe('DELETE /api/v1/users/:id', () => { + test('admin can delete a user', async () => { + const createRes = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'todelete', password: 'pass' }); + const { id } = createRes.body; + + const deleteRes = await authenticatedTestClient(adminToken).delete( + `/api/v1/users/${id}` + ); + expect(deleteRes.status).toBe(204); + + const getRes = await authenticatedTestClient(adminToken).get( + `/api/v1/users/${id}` + ); + expect(getRes.status).toBe(404); + }); + + test('unauthenticated request cannot delete a user', async () => { + const listRes = + await authenticatedTestClient(adminToken).get('/api/v1/users'); + const alice = listRes.body.find((u: { username: string }) => { + return u.username === 'alice'; + }); + + const response = await testClient.delete(`/api/v1/users/${alice.id}`); + + expect(response.status).toBe(401); + }); + + test('non-admin user cannot delete a user', async () => { + const aliceToken = await loginAs('alice', 'alicepass'); + const listRes = + await authenticatedTestClient(adminToken).get('/api/v1/users'); + const admin = listRes.body.find((u: { username: string }) => { + return u.username === 'admin'; + }); + + const response = await authenticatedTestClient(aliceToken).delete( + `/api/v1/users/${admin.id}` + ); + + expect(response.status).toBe(403); + }); + + test('second admin can also delete a user', async () => { + const admin2Token = await loginAs('admin2', 'admin2pass'); + const createRes = await authenticatedTestClient(adminToken) + .post('/api/v1/users') + .send({ username: 'todelete2', password: 'pass' }); + const { id } = createRes.body; + + const response = await authenticatedTestClient(admin2Token).delete( + `/api/v1/users/${id}` + ); + + expect(response.status).toBe(204); + }); + }); +}); diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index ddeb0e87..3e864d46 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -1,6 +1,9 @@ { "extends": "@ttoss/config/tsconfig.json", "compilerOptions": { + "paths": { + "src/*": ["./src/*"] + }, "experimentalDecorators": true, "emitDecoratorMetadata": true }, diff --git a/packages/text-atomizer/CHANGELOG.md b/packages/text-atomizer/CHANGELOG.md deleted file mode 100644 index c87dbaa8..00000000 --- a/packages/text-atomizer/CHANGELOG.md +++ /dev/null @@ -1,10 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -# 0.0.0-alpha.2 (2026-01-06) - -### Bug Fixes - -* add version ([08f4a9f](https://github.com/ttoss/soat/commit/08f4a9f2510947348849b88e5d33b7b44b981986)) diff --git a/packages/text-atomizer/jest.config.ts b/packages/text-atomizer/jest.config.ts deleted file mode 100644 index c94e5234..00000000 --- a/packages/text-atomizer/jest.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { jestRootConfig } from '@ttoss/config'; - -export default jestRootConfig(); diff --git a/packages/text-atomizer/package.json b/packages/text-atomizer/package.json deleted file mode 100644 index d19f0288..00000000 --- a/packages/text-atomizer/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "@soat/text-atomizer", - "version": "0.0.0-alpha.2", - "scripts": { - "build": "tsup", - "test": "jest --projects tests/unit" - }, - "dependencies": { - "ollama": "^0.6.3" - }, - "devDependencies": { - "@ttoss/config": "^1.35.12", - "@types/jest": "^30.0.0", - "jest": "^30.2.0", - "tsup": "^8.5.1", - "tsx": "^4.21.0" - }, - "files": [ - "dist" - ] -} diff --git a/packages/text-atomizer/src/index.ts b/packages/text-atomizer/src/index.ts deleted file mode 100644 index 4e32cf3d..00000000 --- a/packages/text-atomizer/src/index.ts +++ /dev/null @@ -1,151 +0,0 @@ -import ollama from 'ollama'; - -const SEED = 42; - -const SYSTEM_PROMPT = ` -Act as a linguist specialized in syntactic analysis. Your task is to perform a structural decomposition of the sentences provided. - -You must produce a two-level analysis: - -Level 1: Identify and separate the two essential terms of the sentence: the Subject and the Predicate. -Level 2: Decompose the Predicate into its core verb and its dependents. - -Output Format: - -Return a JSON object with the following structure: -{ - "subject": "[Identified Subject]", - "predicate": { - "text": "[Identified Predicate]", - "verb": "[Main verb nucleus of the predicate]", - "complements": ["[Verb complements / required arguments]"] , - "adjuncts": ["[Adjuncts / optional modifiers]"] - } -} - -Rules: - -- Maintain the analysis in the same language as the original sentence. -- If the sentence is impersonal (subjectless), keep the subject field null. -- Be precise in defining the boundary where the subject ends and the predicate begins. -- predicate.text MUST be the full predicate span as it appears in the sentence (everything after the subject), including the verb and all complements/adjuncts (minus final punctuation). -- The predicate.verb must be the main verb nucleus (a verb form as it appears in the sentence). -- Complements are dependents required by the verb (objects, predicatives, required prepositional complements). If none, return an empty array. -- Adjuncts are optional modifiers (adverbials, optional prepositional phrases, temporal/locative/manner phrases, etc). If none, return an empty array. -- Do not invent words. Prefer extracting spans from the original sentence. - -Normalization rules for complements/adjuncts strings: - -- Each item in complements/adjuncts should be the smallest meaningful span (usually a noun phrase). -- If a dependent is introduced by a preposition (e.g., "over the lazy dog"), DO NOT include the preposition in the complements/adjuncts item; include only the object of the preposition (e.g., "the lazy dog"). -- Even when you omit the preposition in the array item, predicate.text must still include the full prepositional phrase as it appears in the sentence. - -Example (follow exactly): - -Input: "The quick brown fox jumps over the lazy dog." -Output: -{ - "subject": "The quick brown fox", - "predicate": { - "text": "jumps over the lazy dog", - "verb": "jumps", - "complements": ["the lazy dog"], - "adjuncts": [] - } -} -`; - -type PredicateDecomposition = { - text: string; - verb: string; - complements: string[]; - adjuncts: string[]; -}; - -type DecompositionResult = { - subject: string | null; - predicate: PredicateDecomposition; -}; - -export const atomizeText = async (args: { text: string }) => { - const response = await ollama.chat({ - model: 'gemma3:1b', - format: 'json', - messages: [ - { role: 'system', content: SYSTEM_PROMPT }, - { - role: 'user', - content: `Analyze and decompose the following sentence:\n\n"${args.text}"`, - }, - ], - options: { - seed: SEED, - temperature: 0, - }, - }); - - const content = response.message.content; - - let result: DecompositionResult; - try { - result = JSON.parse(content) as DecompositionResult; - } catch (error) { - throw new Error( - `Failed to parse model JSON response: ${error instanceof Error ? error.message : String(error)}` - ); - } - - if (typeof result.subject !== 'string' && result.subject !== null) { - throw new Error('Invalid subject in response'); - } - - if ( - typeof result.predicate !== 'object' || - result.predicate === null || - Array.isArray(result.predicate) - ) { - throw new Error('Invalid predicate in response'); - } - - if (typeof result.predicate.text !== 'string') { - throw new Error('Invalid predicate.text in response'); - } - - if (typeof result.predicate.verb !== 'string') { - throw new Error('Invalid predicate.verb in response'); - } - - if (!Array.isArray(result.predicate.complements)) { - throw new Error('Invalid predicate.complements in response'); - } - - if ( - !result.predicate.complements.every((value) => { - return typeof value === 'string'; - }) - ) { - throw new Error('Invalid predicate.complements item in response'); - } - - if (!Array.isArray(result.predicate.adjuncts)) { - throw new Error('Invalid predicate.adjuncts in response'); - } - - if ( - !result.predicate.adjuncts.every((value) => { - return typeof value === 'string'; - }) - ) { - throw new Error('Invalid predicate.adjuncts item in response'); - } - - return result; -}; - -// atomizeText({ text: 'The quick brown fox jumps over the lazy dog.' }).then( -// (result) => { -// if (result) { -// console.log('Decomposed:', result); -// } -// } -// ); diff --git a/packages/text-atomizer/tests/tsconfig.json b/packages/text-atomizer/tests/tsconfig.json deleted file mode 100644 index 397c60cd..00000000 --- a/packages/text-atomizer/tests/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "@ttoss/config/tsconfig.test.json", - "compilerOptions": { - "paths": { - "src/*": ["../src/*"], - "tests/*": ["./*"] - } - } -} diff --git a/packages/text-atomizer/tests/unit/babel.config.cjs b/packages/text-atomizer/tests/unit/babel.config.cjs deleted file mode 100644 index 9c95923b..00000000 --- a/packages/text-atomizer/tests/unit/babel.config.cjs +++ /dev/null @@ -1,5 +0,0 @@ -const { babelConfig } = require('@ttoss/config'); - -const config = babelConfig({}); - -module.exports = config; diff --git a/packages/text-atomizer/tests/unit/jest.config.ts b/packages/text-atomizer/tests/unit/jest.config.ts deleted file mode 100644 index 065c9a60..00000000 --- a/packages/text-atomizer/tests/unit/jest.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { jestUnitConfig } from '@ttoss/config'; - -export default jestUnitConfig(); diff --git a/packages/text-atomizer/tests/unit/tests/atomizeText.test.ts b/packages/text-atomizer/tests/unit/tests/atomizeText.test.ts deleted file mode 100644 index 5208ee1b..00000000 --- a/packages/text-atomizer/tests/unit/tests/atomizeText.test.ts +++ /dev/null @@ -1,370 +0,0 @@ -import { atomizeText } from 'src/index'; - -jest.setTimeout(30000); - -test.each([ - { - // Simple transitive with direct object (phrase) - text: 'The teacher gave the students a difficult test.', - expected: { - discourse: { - speechAct: 'statement', - topic: 'The teacher', - focus: 'a difficult test', - }, - subject: { - text: 'The teacher', - type: 'phrase', - head: 'teacher', - clauseType: null, - appositive: null, - }, - predicate: { - text: 'gave the students a difficult test', - verbalGroup: { - text: 'gave', - mainVerb: 'gave', - auxiliaries: [], - modals: [], - voice: 'active', - tense: 'past', - aspect: 'simple', - }, - directObject: { - text: 'a difficult test', - type: 'phrase', - head: 'test', - clauseType: null, - }, - indirectObject: { - text: 'the students', - type: 'phrase', - head: 'students', - }, - prepositionalObject: null, - subjectComplement: null, - objectComplement: null, - agent: null, - adverbials: [], - }, - }, - }, - { - // Noun clause as direct object - text: 'I believe that she is honest.', - expected: { - discourse: { - speechAct: 'statement', - topic: 'I', - focus: 'that she is honest', - }, - subject: { - text: 'I', - type: 'phrase', - head: 'I', - clauseType: null, - appositive: null, - }, - predicate: { - text: 'believe that she is honest', - verbalGroup: { - text: 'believe', - mainVerb: 'believe', - auxiliaries: [], - modals: [], - voice: 'active', - tense: 'present', - aspect: 'simple', - }, - directObject: { - text: 'that she is honest', - type: 'noun_clause', - head: null, - clauseType: 'that', - }, - indirectObject: null, - prepositionalObject: null, - subjectComplement: null, - objectComplement: null, - agent: null, - adverbials: [], - }, - }, - }, - { - // Infinitive clause as direct object - text: 'I want to leave.', - expected: { - discourse: { speechAct: 'statement', topic: 'I', focus: 'to leave' }, - subject: { - text: 'I', - type: 'phrase', - head: 'I', - clauseType: null, - appositive: null, - }, - predicate: { - text: 'want to leave', - verbalGroup: { - text: 'want', - mainVerb: 'want', - auxiliaries: [], - modals: [], - voice: 'active', - tense: 'present', - aspect: 'simple', - }, - directObject: { - text: 'to leave', - type: 'infinitive_clause', - head: null, - clauseType: 'to', - }, - indirectObject: null, - prepositionalObject: null, - subjectComplement: null, - objectComplement: null, - agent: null, - adverbials: [], - }, - }, - }, - { - // Prepositional object (verb-required PP) - text: 'He depends on you.', - expected: { - discourse: { speechAct: 'statement', topic: 'He', focus: 'on you' }, - subject: { - text: 'He', - type: 'phrase', - head: 'He', - clauseType: null, - appositive: null, - }, - predicate: { - text: 'depends on you', - verbalGroup: { - text: 'depends', - mainVerb: 'depend', - auxiliaries: [], - modals: [], - voice: 'active', - tense: 'present', - aspect: 'simple', - }, - directObject: null, - indirectObject: null, - prepositionalObject: { - text: 'on you', - preposition: 'on', - object: { text: 'you', head: 'you' }, - }, - subjectComplement: null, - objectComplement: null, - agent: null, - adverbials: [], - }, - }, - }, - { - // Passive voice with agent - text: 'The cake was eaten by John.', - expected: { - discourse: { - speechAct: 'statement', - topic: 'The cake', - focus: 'by John', - }, - subject: { - text: 'The cake', - type: 'phrase', - head: 'cake', - clauseType: null, - appositive: null, - }, - predicate: { - text: 'was eaten by John', - verbalGroup: { - text: 'was eaten', - mainVerb: 'eat', - auxiliaries: ['was'], - modals: [], - voice: 'passive', - tense: 'past', - aspect: 'simple', - }, - directObject: null, - indirectObject: null, - prepositionalObject: null, - subjectComplement: null, - objectComplement: null, - agent: { text: 'by John', head: 'John' }, - adverbials: [], - }, - }, - }, - { - // Gerund as subject - text: 'Running is healthy.', - expected: { - discourse: { speechAct: 'statement', topic: 'Running', focus: 'healthy' }, - subject: { - text: 'Running', - type: 'gerund_clause', - head: null, - clauseType: null, - appositive: null, - }, - predicate: { - text: 'is healthy', - verbalGroup: { - text: 'is', - mainVerb: 'be', - auxiliaries: [], - modals: [], - voice: 'active', - tense: 'present', - aspect: 'simple', - }, - directObject: null, - indirectObject: null, - prepositionalObject: null, - subjectComplement: { - text: 'healthy', - type: 'phrase', - head: 'healthy', - clauseType: null, - }, - objectComplement: null, - agent: null, - adverbials: [], - }, - }, - }, - { - // Appositive - text: 'My friend, a doctor, is here.', - expected: { - discourse: { speechAct: 'statement', topic: 'My friend', focus: 'here' }, - subject: { - text: 'My friend', - type: 'phrase', - head: 'friend', - clauseType: null, - appositive: { text: 'a doctor', head: 'doctor' }, - }, - predicate: { - text: 'is here', - verbalGroup: { - text: 'is', - mainVerb: 'be', - auxiliaries: [], - modals: [], - voice: 'active', - tense: 'present', - aspect: 'simple', - }, - directObject: null, - indirectObject: null, - prepositionalObject: null, - subjectComplement: null, - objectComplement: null, - agent: null, - adverbials: [ - { - text: 'here', - type: 'phrase', - head: 'here', - clauseType: null, - semanticRole: 'place', - }, - ], - }, - }, - }, - { - // Present perfect progressive (complex verbal group) - text: 'She has been working hard.', - expected: { - discourse: { speechAct: 'statement', topic: 'She', focus: 'hard' }, - subject: { - text: 'She', - type: 'phrase', - head: 'She', - clauseType: null, - appositive: null, - }, - predicate: { - text: 'has been working hard', - verbalGroup: { - text: 'has been working', - mainVerb: 'work', - auxiliaries: ['has', 'been'], - modals: [], - voice: 'active', - tense: 'present', - aspect: 'perfect_progressive', - }, - directObject: null, - indirectObject: null, - prepositionalObject: null, - subjectComplement: null, - objectComplement: null, - agent: null, - adverbials: [ - { - text: 'hard', - type: 'phrase', - head: 'hard', - clauseType: null, - semanticRole: 'manner', - }, - ], - }, - }, - }, - { - // Participle clause as adverbial - text: 'Having finished the work, she left.', - expected: { - discourse: { speechAct: 'statement', topic: 'she', focus: 'left' }, - subject: { - text: 'she', - type: 'phrase', - head: 'she', - clauseType: null, - appositive: null, - }, - predicate: { - text: 'left', - verbalGroup: { - text: 'left', - mainVerb: 'leave', - auxiliaries: [], - modals: [], - voice: 'active', - tense: 'past', - aspect: 'simple', - }, - directObject: null, - indirectObject: null, - prepositionalObject: null, - subjectComplement: null, - objectComplement: null, - agent: null, - adverbials: [ - { - text: 'Having finished the work', - type: 'participle_clause', - head: null, - clauseType: null, - semanticRole: 'time', - }, - ], - }, - }, - }, -])('atomizeText correctly decomposes: "$text"', async ({ text, expected }) => { - const result = await atomizeText({ text }); - expect(result).toEqual(expected); -}); diff --git a/packages/text-atomizer/tsconfig.json b/packages/text-atomizer/tsconfig.json deleted file mode 100644 index fce4d054..00000000 --- a/packages/text-atomizer/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "@ttoss/config/tsconfig.json" -} diff --git a/packages/text-atomizer/tsup.config.ts b/packages/text-atomizer/tsup.config.ts deleted file mode 100644 index 76e36f0f..00000000 --- a/packages/text-atomizer/tsup.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { tsupConfig } from '@ttoss/config'; - -export const tsup = tsupConfig(); diff --git a/packages/website/blog/2019-05-28-first-blog-post.md b/packages/website/blog/2019-05-28-first-blog-post.md deleted file mode 100644 index d3032efb..00000000 --- a/packages/website/blog/2019-05-28-first-blog-post.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -slug: first-blog-post -title: First Blog Post -authors: [slorber, yangshun] -tags: [hola, docusaurus] ---- - -Lorem ipsum dolor sit amet... - - - -...consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet diff --git a/packages/website/blog/2019-05-29-long-blog-post.md b/packages/website/blog/2019-05-29-long-blog-post.md deleted file mode 100644 index eb4435de..00000000 --- a/packages/website/blog/2019-05-29-long-blog-post.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -slug: long-blog-post -title: Long Blog Post -authors: yangshun -tags: [hello, docusaurus] ---- - -This is the summary of a very long blog post, - -Use a `` comment to limit blog post size in the list view. - - - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet diff --git a/packages/website/blog/2021-08-01-mdx-blog-post.mdx b/packages/website/blog/2021-08-01-mdx-blog-post.mdx deleted file mode 100644 index 0c4b4a48..00000000 --- a/packages/website/blog/2021-08-01-mdx-blog-post.mdx +++ /dev/null @@ -1,24 +0,0 @@ ---- -slug: mdx-blog-post -title: MDX Blog Post -authors: [slorber] -tags: [docusaurus] ---- - -Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/). - -:::tip - -Use the power of React to create interactive blog posts. - -::: - -{/* truncate */} - -For example, use JSX to create an interactive button: - -```js - -``` - - diff --git a/packages/website/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg b/packages/website/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg deleted file mode 100644 index 11bda092..00000000 Binary files a/packages/website/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg and /dev/null differ diff --git a/packages/website/blog/2021-08-26-welcome/index.md b/packages/website/blog/2021-08-26-welcome/index.md deleted file mode 100644 index 349ea075..00000000 --- a/packages/website/blog/2021-08-26-welcome/index.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -slug: welcome -title: Welcome -authors: [slorber, yangshun] -tags: [facebook, hello, docusaurus] ---- - -[Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog). - -Here are a few tips you might find useful. - - - -Simply add Markdown files (or folders) to the `blog` directory. - -Regular blog authors can be added to `authors.yml`. - -The blog post date can be extracted from filenames, such as: - -- `2019-05-30-welcome.md` -- `2019-05-30-welcome/index.md` - -A blog post folder can be convenient to co-locate blog post images: - -![Docusaurus Plushie](./docusaurus-plushie-banner.jpeg) - -The blog supports tags as well! - -**And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config. diff --git a/packages/website/blog/authors.yml b/packages/website/blog/authors.yml deleted file mode 100644 index 0fd39873..00000000 --- a/packages/website/blog/authors.yml +++ /dev/null @@ -1,25 +0,0 @@ -yangshun: - name: Yangshun Tay - title: Ex-Meta Staff Engineer, Co-founder GreatFrontEnd - url: https://linkedin.com/in/yangshun - image_url: https://github.com/yangshun.png - page: true - socials: - x: yangshunz - linkedin: yangshun - github: yangshun - newsletter: https://www.greatfrontend.com - -slorber: - name: Sébastien Lorber - title: Docusaurus maintainer - url: https://sebastienlorber.com - image_url: https://github.com/slorber.png - page: - # customize the url of the author page at /blog/authors/ - permalink: '/all-sebastien-lorber-articles' - socials: - x: sebastienlorber - linkedin: sebastienlorber - github: slorber - newsletter: https://thisweekinreact.com diff --git a/packages/website/blog/tags.yml b/packages/website/blog/tags.yml deleted file mode 100644 index bfaa778f..00000000 --- a/packages/website/blog/tags.yml +++ /dev/null @@ -1,19 +0,0 @@ -facebook: - label: Facebook - permalink: /facebook - description: Facebook tag description - -hello: - label: Hello - permalink: /hello - description: Hello tag description - -docusaurus: - label: Docusaurus - permalink: /docusaurus - description: Docusaurus tag description - -hola: - label: Hola - permalink: /hola - description: Hola tag description diff --git a/packages/website/carlin.yml b/packages/website/carlin.yml new file mode 100644 index 00000000..58e9fc70 --- /dev/null +++ b/packages/website/carlin.yml @@ -0,0 +1 @@ +appendIndexHtml: true diff --git a/packages/website/docs/Introduction.md b/packages/website/docs/Introduction.md new file mode 100644 index 00000000..4a9105c5 --- /dev/null +++ b/packages/website/docs/Introduction.md @@ -0,0 +1,5 @@ +--- +sidebar_position: 1 +--- + +# Introduction diff --git a/packages/website/docs/getting-started.md b/packages/website/docs/getting-started.md index c410c618..30161d89 100644 --- a/packages/website/docs/getting-started.md +++ b/packages/website/docs/getting-started.md @@ -3,92 +3,3 @@ sidebar_position: 2 --- # Getting Started - -The fastest way to run SOAT is using Docker Compose. This ensures you have the SOAT API Server and the vector-enabled PostgreSQL database running together correctly. - -## Prerequisites - -- [Docker Desktop](https://www.docker.com/products/docker-desktop/) or Docker Engine installed. -- `git` (optional, for cloning the repo). - -## Quick Start - -1. **Clone the Repository** - - ```bash - git clone https://github.com/ttoss/soat.git - cd soat - ``` - -2. **Start the Services** - - We provide a standard `docker-compose.yml` configuration (create this file in your root folder if it doesn't exist, based on the example below). - - Create a `docker-compose.yml` in the root of your project: - - ```yaml - services: - database: - image: pgvector/pgvector:0.8.1-pg18-trixie - container_name: soat-database - environment: - POSTGRES_DB: soat_db - POSTGRES_USER: soat_user - POSTGRES_PASSWORD: soat_password - ports: - - '5432:5432' - volumes: - - postgres_data:/var/lib/postgresql/data - healthcheck: - test: ['CMD-SHELL', 'pg_isready -U soat_user -d soat_db'] - interval: 10s - timeout: 5s - retries: 5 - - server: - image: ghcr.io/ttoss/soat-server:latest # Assuming a published image, or build locally - # For local development, you might build from ./packages/server - # build: ./packages/server - container_name: soat-server - ports: - - '3000:3000' - environment: - DATABASE_URL: postgres://soat_user:soat_password@database:5432/soat_db - PORT: 3000 - depends_on: - database: - condition: service_healthy - - volumes: - postgres_data: - ``` - - > **Note:** If you are running from source, you may need to build the server package locally. - -3. **Run Docker Compose** - - ```bash - docker-compose up -d - ``` - - Your SOAT Server should now be running at `http://localhost:3000`. - -4. **Verify Installation** - - You can test if the server is running by checking the health endpoint or documentation (if enabled): - - ```bash - curl http://localhost:3000/health - ``` - -## Environment Variables - -The server behaves differently based on configuration. Important variables include: - -- `DATABASE_URL`: Connection string for PostgreSQL. -- `OPENAI_API_KEY`: Required if you are using OpenAI for embeddings (default). -- `OLLAMA_HOST`: (Optional) URL for local Ollama instance if using local embeddings. - -## Next Steps - -Now that your server is running, let's **[Connect an Agent](./tutorials/connect-mcp.md)** to it! diff --git a/packages/website/docs/intro.md b/packages/website/docs/intro.md deleted file mode 100644 index ff2d3990..00000000 --- a/packages/website/docs/intro.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Introduction - -**SOAT (Source of Agentic Truth)** is an open-source framework designed to give AI agents **Persistent Memory**. - -In the evolving landscape of AI, agents are becoming more autonomous and capable. However, they often suffer from "amnesia" between sessions or have limited context windows. SOAT solves this by providing a dedicated memory server that allows agents to store and retrieve information semantically. - -## Why SOAT? - -- **🧠 Persistent Memory**: Agents can store text, files, and structured data that survives across sessions. -- **🔎 Semantic Search**: Built on `pgvector`, SOAT enables agents to find information not just by keywords, but by _meaning_. -- **🔌 MCP Native**: Full support for the **Model Context Protocol (MCP)**, allowing seamless integration with Claude Desktop, Cursor, and other MCP-compliant tools. -- **⚡ Simple API**: A standardized REST API for building custom integrations. - -## Core Concepts - -### Memory & Embeddings - -When you send text to SOAT, it doesn't just save the string. It generates a **vector embedding**—a mathematical representation of the text's meaning. This allows the system to calculate similarity between different pieces of information. - -### The MCP Server - -The Model Context Protocol (MCP) is a standard for connecting AI models to external data. SOAT acts as an MCP Server, exposing tools like `add_memory` and `search_memory` that agents can discover and use automatically. - -## Next Steps - -- **[Getting Started](./getting-started.md)**: Spin up your own SOAT server in minutes using Docker. -- **[Connect with MCP](./tutorials/connect-mcp.md)**: Learn how to connect Claude Desktop to your new memory bank. -- **[Storing Memory](./tutorials/storing-memory.md)**: A guide to the memory tools available to your agents. diff --git a/packages/website/docs/modules/actors.md b/packages/website/docs/modules/actors.md new file mode 100644 index 00000000..82cc22d3 --- /dev/null +++ b/packages/website/docs/modules/actors.md @@ -0,0 +1,45 @@ +# Actors Module + +The Actors module represents entities (people, bots, or other participants) that interact within a project. A common use case is storing WhatsApp contacts, where `externalId` holds the phone number. + +## Overview + +An Actor belongs to a project and has a display name, an optional type, and an optional `externalId`. The `externalId` is unique within a project and is designed for correlating actors with external systems — for example, mapping a WhatsApp phone number to a known contact. + +Actors are identified by an `id` prefixed with `act_`. The internal database primary key is never returned. + +## Data Model + +| Field | Type | Description | +| ------------ | ------ | ------------------------------------------------------------------------------------ | +| `id` | string | Public identifier prefixed with `act_` | +| `projectId` | string | ID of the owning project | +| `name` | string | Display name of the actor | +| `type` | string | Optional actor type (e.g. `customer`, `agent`) | +| `externalId` | string | Optional external identifier (e.g. WhatsApp phone number). Unique within the project | +| `createdAt` | string | ISO 8601 creation timestamp | +| `updatedAt` | string | ISO 8601 last-updated timestamp | + +## Key Concepts + +### externalId + +`externalId` is a free-form string that lets you correlate an Actor with a record in an external system. It is enforced unique per project at the database level — two actors in the same project cannot share the same `externalId`. Across different projects, the same `externalId` value is allowed. + +A `null` / absent `externalId` is never considered a duplicate — PostgreSQL's NULL semantics are preserved. + +### Filtering + +`GET /actors` accepts an optional `externalId` query parameter. This lets you look up an actor by their external identifier (e.g. resolve a WhatsApp number to an Actor record) without knowing their `act_` ID. + +## Permissions + +Actor operations are governed by per-project policies. Grant the following permissions: + +| Action | Permission | REST Endpoint | MCP Tool | +| --------------- | -------------------- | --------------------------- | -------------- | +| List actors | `actors:ListActors` | `GET /api/v1/actors` | `list-actors` | +| Get actor by ID | `actors:GetActor` | `GET /api/v1/actors/:id` | `get-actor` | +| Create actor | `actors:CreateActor` | `POST /api/v1/actors` | `create-actor` | +| Update actor | `actors:UpdateActor` | `PATCH /api/v1/actors/:id` | `update-actor` | +| Delete actor | `actors:DeleteActor` | `DELETE /api/v1/actors/:id` | `delete-actor` | diff --git a/packages/website/docs/modules/ai-providers.md b/packages/website/docs/modules/ai-providers.md new file mode 100644 index 00000000..230d8698 --- /dev/null +++ b/packages/website/docs/modules/ai-providers.md @@ -0,0 +1,51 @@ +# AI Providers Module + +The AI Providers module lets you register and manage LLM provider configurations for a project. Each provider record stores the model slug, optional base URL, optional configuration, and an optional link to a [Secret](./secrets.md) that supplies the API key. + +## Overview + +An AI provider is a named configuration that tells the system how to reach a specific LLM endpoint. A project can have multiple providers — for example, one for GPT-4o and another for Claude 3.5. + +When a provider is linked to a secret the secret's encrypted value is retrieved and passed as the API key when calling the LLM. The key is never exposed through the API. + +## Data Model + +| Field | Type | Description | +| -------------- | ---------------- | --------------------------------------------------------- | +| `id` | string | Public identifier (e.g. `aip_…`) | +| `projectId` | string | ID of the owning project | +| `secretId` | string \| null | Public ID of the linked secret, or `null` | +| `name` | string | Human-readable label | +| `provider` | `AiProviderSlug` | Provider slug (see below) | +| `defaultModel` | string | Default model name sent to the provider API | +| `baseUrl` | string \| null | Override base URL (optional, useful for self-hosted LLMs) | +| `config` | object \| null | Arbitrary provider-specific configuration object | +| `createdAt` | string | ISO 8601 creation timestamp | +| `updatedAt` | string | ISO 8601 last-updated timestamp | + +### Provider Slugs + +Valid values for the `provider` field: + +| Slug | Description | +| ----------- | -------------------------- | +| `openai` | OpenAI | +| `anthropic` | Anthropic | +| `google` | Google Gemini | +| `xai` | xAI (Grok) | +| `groq` | Groq | +| `ollama` | Ollama (local) | +| `azure` | Azure OpenAI | +| `bedrock` | Amazon Bedrock | +| `gateway` | Generic API gateway | +| `custom` | Custom / self-hosted model | + +## Permissions + +| Action | Permission | REST Endpoint | MCP Tool | +| --------------- | ------------------------------ | ------------------------------------------- | -------------------- | +| List providers | `aiProviders:ListAiProviders` | `GET /api/v1/ai-providers` | `list-ai-providers` | +| Get a provider | `aiProviders:GetAiProvider` | `GET /api/v1/ai-providers/:aiProviderId` | `get-ai-provider` | +| Create provider | `aiProviders:CreateAiProvider` | `POST /api/v1/ai-providers` | `create-ai-provider` | +| Update provider | `aiProviders:UpdateAiProvider` | `PATCH /api/v1/ai-providers/:aiProviderId` | `update-ai-provider` | +| Delete provider | `aiProviders:DeleteAiProvider` | `DELETE /api/v1/ai-providers/:aiProviderId` | `delete-ai-provider` | diff --git a/packages/website/docs/modules/conversations.md b/packages/website/docs/modules/conversations.md new file mode 100644 index 00000000..9d513f80 --- /dev/null +++ b/packages/website/docs/modules/conversations.md @@ -0,0 +1,64 @@ +# Conversations Module + +The Conversations module represents a series of messages exchanged with an Actor within a project. Conversations group documents (messages) in an ordered sequence, tracking the dialogue between a system and an actor such as a WhatsApp contact. + +## Overview + +A Conversation belongs to a project and is associated with an Actor. It has a status (`open` or `closed`) and contains an ordered list of messages, where each message is a reference to a Document along with its position in the conversation. + +Conversations are identified by an `id` prefixed with `conv_`. The internal database primary key is never returned. + +## Data Model + +### Conversation + +| Field | Type | Description | +| ----------- | ------ | --------------------------------------------------- | +| `id` | string | Public identifier prefixed with `conv_` | +| `projectId` | string | ID of the owning project | +| `actorId` | string | ID of the Actor this conversation belongs to | +| `status` | string | Conversation status: `open` or `closed` | +| `createdAt` | string | ISO 8601 creation timestamp | +| `updatedAt` | string | ISO 8601 last-updated timestamp | + +### Conversation Message + +| Field | Type | Description | +| ------------ | ------- | -------------------------------------------------------------------- | +| `documentId` | string | ID of the Document attached as a message | +| `position` | integer | Zero-based position of the message in the conversation | +| `content` | string | Full text content of the message (read from the underlying document) | + +## Key Concepts + +### Messages + +Messages are ordered references to Documents within a conversation. When adding a message, you can specify an explicit `position`. If omitted, the document is appended at the end (position = MAX + 1). Each document can appear at most once per conversation — adding the same document twice returns `409 Conflict`. + +When listing messages, each entry includes the full text `content` of the underlying document. + +Removing a message from a conversation also deletes its underlying Document and the associated File on disk, preventing orphaned records. + +### Status + +A conversation transitions between `open` and `closed`. Use `PATCH /conversations/:id` to update the status. New conversations default to `open`. + +### Actor Association + +Every conversation is linked to a single Actor. You can filter conversations by `actorId` using the `GET /conversations?actorId=` query parameter to retrieve all conversations for a specific contact. + +## Permissions + +Conversation operations are governed by per-project policies. Grant the following permissions: + +| Action | Permission | REST Endpoint | MCP Tool | +| -------------------------------- | ---------------------------------- | ------------------------------------------- | ----------------------------- | +| List conversations | `conversations:ListConversations` | `GET /api/v1/conversations` | `list-conversations` | +| Get conversation by ID | `conversations:GetConversation` | `GET /api/v1/conversations/:id` | `get-conversation` | +| List conversation messages | `conversations:GetConversation` | `GET /api/v1/conversations/:id/messages` | `list-conversation-messages` | +| List conversation actors | `conversations:GetConversation` | `GET /api/v1/conversations/:id/actors` | `list-conversation-actors` | +| Create conversation | `conversations:CreateConversation` | `POST /api/v1/conversations` | `create-conversation` | +| Update conversation status | `conversations:UpdateConversation` | `PATCH /api/v1/conversations/:id` | `update-conversation` | +| Add message to conversation | `conversations:UpdateConversation` | `POST /api/v1/conversations/:id/messages` | `add-conversation-message` | +| Remove message from conversation | `conversations:UpdateConversation` | `DELETE /api/v1/conversations/:id/messages` | `remove-conversation-message` | +| Delete conversation | `conversations:DeleteConversation` | `DELETE /api/v1/conversations/:id` | `delete-conversation` | diff --git a/packages/website/docs/modules/documents.md b/packages/website/docs/modules/documents.md new file mode 100644 index 00000000..c3412443 --- /dev/null +++ b/packages/website/docs/modules/documents.md @@ -0,0 +1,93 @@ +# Documents Module + +The Documents module stores plain-text documents along with an embedding vector in PostgreSQL, enabling semantic (vector) search across project content. Under the hood each document is backed by a Files record stored on disk. + +## Overview + +A Document IS a File — it always uses `.txt` format and is associated with a project. When a document is created, its text content is passed to a configured embedding provider (currently Ollama only), and the resulting vector is stored alongside the text. This allows cosine-similarity search at query time without an external vector database. + +Documents are identified by an `id` prefixed with `doc_`. The internal database primary key is never returned. + +## Configuration + +| Environment Variable | Required | Description | +| ---------------------- | -------- | ------------------------------------------------------------ | +| `FILES_STORAGE_DIR` | Yes | Directory where `.txt` files are written (shared with Files) | +| `EMBEDDING_PROVIDER` | Yes | Embedding backend — only `ollama` is supported | +| `EMBEDDING_MODEL` | Yes | Model name, e.g. `qwen3-embedding:0.6b` | +| `EMBEDDING_DIMENSIONS` | Yes | Vector dimensions — must match the model output, e.g. `1024` | +| `OLLAMA_BASE_URL` | No | Ollama server URL, defaults to `http://localhost:11434` | + +### Ollama setup example + +```bash +# Pull the embedding model +ollama pull qwen3-embedding:0.6b + +# Verify it's running +ollama list +``` + +Set the server environment variables: + +```env +EMBEDDING_PROVIDER=ollama +EMBEDDING_MODEL=qwen3-embedding:0.6b +EMBEDDING_DIMENSIONS=1024 +OLLAMA_BASE_URL=http://localhost:11434 +``` + +## Data Model + +| Field | Type | Description | +| ----------- | ------ | ------------------------------------------------------------- | +| `id` | string | Public identifier prefixed with `doc_` | +| `fileId` | string | ID of the underlying File record | +| `projectId` | string | ID of the owning project | +| `filename` | string | Original filename (`.txt` extension) | +| `size` | number | File size in bytes | +| `content` | string | Text content — only present in `GET /documents/:id` responses | +| `createdAt` | string | ISO 8601 creation timestamp | +| `updatedAt` | string | ISO 8601 last-updated timestamp | + +The `embedding` column (pgvector `vector(N)`) is stored in the database but never returned via the API. + +## Permissions + +Document operations are governed by per-project policies. Grant the following permissions: + +| Action | Permission | REST Endpoint | MCP Tool | +| ----------------- | --------------------------- | ------------------------------- | ------------------ | +| List documents | `documents:ListDocuments` | `GET /api/v1/documents` | `list-documents` | +| Get a document | `documents:GetDocument` | `GET /api/v1/documents/:id` | `get-document` | +| Create a document | `documents:CreateDocument` | `POST /api/v1/documents` | `create-document` | +| Delete a document | `documents:DeleteDocument` | `DELETE /api/v1/documents/:id` | `delete-document` | +| Update a document | `documents:UpdateDocument` | `PATCH /api/v1/documents/:id` | `update-document` | +| Semantic search | `documents:SearchDocuments` | `POST /api/v1/documents/search` | `search-documents` | + +See the [API Reference](../api/documents/list-documents) for full endpoint details, request/response schemas, and status codes. + +## Project ID Resolution + +For endpoints that accept `projectId`, the field is optional. When omitted, the server resolves accessible projects based on the caller's identity: + +| Caller type | Behavior when `projectId` is omitted | +| ----------- | ---------------------------------------------------------------------------- | +| project key | Infers the project from the key's own scope (single project) | +| JWT admin | No project filter — returns results across all projects | +| JWT user | Enumerates all projects the user is a member of with the required permission | + +If `projectId` is supplied but the caller lacks permission for that project, the request returns `403 Forbidden`. + +## MCP Tools + +The following MCP tools are available for AI assistants: + +| Tool name | Description | +| ------------------ | -------------------------------------------------------------------------- | +| `list-documents` | List documents; omit `projectId` to retrieve all accessible documents | +| `get-document` | Retrieve a document including its text content | +| `create-document` | Create a new text document with automatic embedding | +| `delete-document` | Delete a document and its underlying file | +| `update-document` | Update document content, title, metadata, or tags | +| `search-documents` | Semantic search; omit `projectId` to search across all accessible projects | diff --git a/packages/website/docs/modules/files.md b/packages/website/docs/modules/files.md index f0fe1def..c1dc16d7 100644 --- a/packages/website/docs/modules/files.md +++ b/packages/website/docs/modules/files.md @@ -1,328 +1,59 @@ # Files Module -The Files module (`@soat/files-core`) provides a flexible file storage and management system with support for multiple storage backends. It handles file uploads, retrieval, deletion, and metadata tracking through a unified interface. +The Files module provides file upload, download, metadata management, and deletion through a local filesystem storage backend. Files are stored in a configurable directory and tracked in PostgreSQL. ## Overview -The Files module abstracts storage operations across different backends, allowing you to switch between local filesystem, AWS S3, and Google Cloud Storage without changing your application code. It automatically tracks file metadata in PostgreSQL for efficient querying and management. +Files are associated with a project and stored at `{FILES_STORAGE_DIR}/{id}{ext}` on the server's local filesystem. Every file record exposes an `id` — the internal database primary key is never returned. -## Installation +## Configuration -```bash -pnpm add @soat/files-core -``` - -## Storage Backends - -### Local Storage - -Store files on the local filesystem. - -```typescript -const config: StorageConfig = { - type: 'local', - local: { - path: '/path/to/storage', - }, -}; -``` - -### AWS S3 - -Store files in Amazon S3 buckets. - -```typescript -const config: StorageConfig = { - type: 's3', - s3: { - bucket: 'my-bucket', - region: 'us-east-1', - accessKeyId: process.env.AWS_ACCESS_KEY_ID, - secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, - }, -}; -``` - -### Google Cloud Storage - -Store files in Google Cloud Storage buckets. - -```typescript -const config: StorageConfig = { - type: 'gcs', - gcs: { - bucket: 'my-bucket', - projectId: 'my-project-id', - keyFilename: '/path/to/service-account-key.json', // optional - }, -}; -``` - -## Core Functions - -### saveFile - -Save content directly to storage with optional metadata. - -```typescript -import { saveFile } from '@soat/files-core'; - -const file = await saveFile({ - config, - content: 'Hello, world!', // string or Buffer - options: { - contentType: 'text/plain', - metadata: { - author: 'John Doe', - tags: ['greeting', 'example'], - }, - }, -}); - -console.log(file.id); // UUID of saved file -``` - -**Parameters:** - -- `config`: Storage configuration object -- `content`: String or Buffer containing file data -- `options` (optional): - - `contentType`: MIME type of the content - - `metadata`: Key-value pairs for custom metadata - -**Returns:** `FileData` object with `id` and `content` - -### uploadFile - -Upload a file from the local filesystem to storage. - -```typescript -import { uploadFile } from '@soat/files-core'; - -const file = await uploadFile({ - config, - filePath: '/path/to/local/file.pdf', - options: { - contentType: 'application/pdf', - metadata: { - filename: 'document.pdf', - category: 'invoices', - }, - }, -}); -``` - -**Parameters:** - -- `config`: Storage configuration object -- `filePath`: Absolute path to the file to upload -- `options` (optional): Same as `saveFile` - -**Returns:** `FileData` object with `id` and `content` - -### retrieveFileById - -Retrieve a file's content by its ID. - -```typescript -import { retrieveFileById } from '@soat/files-core'; - -const file = await retrieveFileById({ - config, - id: 'file-uuid-here', -}); - -if (file) { - console.log(file.content); // Buffer or string -} else { - console.log('File not found'); -} -``` - -**Parameters:** - -- `config`: Storage configuration object -- `id`: UUID of the file to retrieve - -**Returns:** `FileData | null` - -### deleteFile - -Delete a file from both storage and database. - -```typescript -import { deleteFile } from '@soat/files-core'; - -const deleted = await deleteFile({ - config, - id: 'file-uuid-here', -}); - -console.log(deleted ? 'File deleted' : 'File not found'); -``` - -**Parameters:** - -- `config`: Storage configuration object -- `id`: UUID of the file to delete - -**Returns:** `boolean` indicating success - -### listFileRecords - -List all file records from the database with metadata. - -```typescript -import { listFileRecords } from '@soat/files-core'; - -const files = await listFileRecords(); - -files.forEach((file) => { - console.log(file.id, file.filename, file.size, file.createdAt); -}); -``` - -**Returns:** Array of `FileRecord` objects - -### getFileRecord - -Get a specific file's metadata record. - -```typescript -import { getFileRecord } from '@soat/files-core'; - -const record = await getFileRecord('file-uuid-here'); - -if (record) { - console.log(record.filename, record.contentType, record.size); -} -``` - -**Parameters:** - -- `id`: UUID of the file - -**Returns:** `FileRecord | null` - -## Type Definitions - -### StorageConfig - -Configuration for storage backend selection. +| Environment Variable | Required | Description | +| -------------------- | -------- | ------------------------------------------------------------------------------------------------------- | +| `FILES_STORAGE_DIR` | Yes | Absolute path to the directory where uploaded files are stored. Must be writable by the server process. | -```typescript -interface StorageConfig { - type: 'local' | 's3' | 'gcs'; - local?: { - path: string; - }; - s3?: { - bucket: string; - region: string; - accessKeyId: string; - secretAccessKey: string; - }; - gcs?: { - bucket: string; - keyFilename?: string; - projectId?: string; - }; -} -``` - -### UploadOptions - -Options for file upload operations. - -```typescript -interface UploadOptions { - contentType?: string; - metadata?: Record; -} -``` - -### FileData +When running via Docker, mount a volume at this path to persist files across container restarts: -Returned data after file operations. +```yaml +services: + server: + image: soat-server + environment: + FILES_STORAGE_DIR: /data/files + volumes: + - files-data:/data/files -```typescript -interface FileData { - id: string; - content: string | Buffer; -} +volumes: + files-data: ``` -### FileRecord - -Database record for file metadata. - -```typescript -interface FileRecord { - id: string; - filename?: string; - contentType?: string; - size?: number; - storageType: 'local' | 's3' | 'gcs'; - storagePath: string; - metadata?: Record; - createdAt: Date; - updatedAt: Date; -} -``` - -## Usage with Server - -The Files module is integrated into the SOAT server REST API at `/v1/files`: - -- `GET /v1/files` - List all files -- `POST /v1/files/upload` - Upload a file -- `GET /v1/files/:id` - Retrieve file by ID -- `DELETE /v1/files/:id` - Delete file by ID +## Data Model -See the [API documentation](/docs/api/files/soat-files-api) for details. +| Field | Type | Description | +| ------------- | ------------------------ | ----------------------------------------- | +| `id` | string | Public identifier | +| `filename` | string | Original filename | +| `contentType` | string | MIME type | +| `size` | number | File size in bytes | +| `storageType` | `local` \| `s3` \| `gcs` | Storage backend (currently `local`) | +| `storagePath` | string | Absolute path on disk | +| `metadata` | string | Arbitrary JSON string for custom metadata | +| `projectId` | string | ID of the owning project | +| `createdAt` | string | ISO 8601 creation timestamp | +| `updatedAt` | string | ISO 8601 last-updated timestamp | -## Best Practices +## Permissions -### Security - -- **Never commit credentials** - Use environment variables for S3/GCS credentials -- **Validate file types** - Check `contentType` before processing -- **Limit file sizes** - Implement size limits at the API level -- **Sanitize metadata** - Validate and sanitize user-provided metadata - -### Performance - -- **Use buffers for binary data** - More efficient than base64 strings -- **Stream large files** - For files >10MB, consider streaming implementations -- **Cache metadata** - File records are in PostgreSQL, suitable for caching - -### Storage Selection - -- **Local**: Development, small-scale deployments, low latency requirements -- **S3**: Production, scalable, CDN integration, global availability -- **GCS**: Google Cloud ecosystem, machine learning pipelines - -## Error Handling - -All functions may throw errors for: - -- Invalid configuration -- Storage backend connectivity issues -- Filesystem permission errors -- Database connection failures - -Wrap operations in try-catch blocks: - -```typescript -try { - const file = await saveFile({ config, content: data }); - console.log('Saved:', file.id); -} catch (error) { - console.error('Failed to save file:', error.message); -} -``` +File operations are governed by per-project policies. Grant the following permissions to allow a user to perform each action: -## Next Steps +| Action | Permission | REST Endpoint | MCP Tool | +| ----------------------------- | -------------------------- | ---------------------------------- | ---------------------- | +| List files | `files:GetFile` | `GET /api/v1/files` | `list-files` | +| Get file metadata | `files:GetFile` | `GET /api/v1/files/:id` | `get-file` | +| Create a metadata-only record | `files:CreateFile` | `POST /api/v1/files` | `create-file` | +| Upload a file | `files:UploadFile` | `POST /api/v1/files/upload` | `upload-file` | +| Download file content | `files:DownloadFile` | `GET /api/v1/files/:id/download` | `download-file` | +| Update metadata | `files:UpdateFileMetadata` | `PATCH /api/v1/files/:id/metadata` | `update-file-metadata` | +| Delete a file | `files:DeleteFile` | `DELETE /api/v1/files/:id` | `delete-file` | -- [Getting Started](/docs/getting-started) - Set up SOAT server with file storage -- [API Reference](/docs/api/files/soat-files-api) - REST API endpoints for files +See the [API Reference](../api/files/list-files) for full endpoint details, request/response schemas, and status codes. diff --git a/packages/website/docs/modules/iam.md b/packages/website/docs/modules/iam.md new file mode 100644 index 00000000..ec1a5c20 --- /dev/null +++ b/packages/website/docs/modules/iam.md @@ -0,0 +1,323 @@ +--- +sidebar_position: 1 +--- + +# IAM + +The IAM (Identity and Access Management) module provides authentication, identity management, and fine-grained authorization for the SOAT platform. It implements an AWS IAM-inspired policy engine with structured policy statements supporting `Effect`, `Action`, `Resource`, and `Condition`. + +## Overview + +SOAT uses a policy-based access control model. Every API request is authenticated via JWT (for users) or project key (for project-scoped clients). Authorization is evaluated by loading the caller's attached policy documents and running them through the policy engine. + +The IAM module covers: + +- **Users** — identity management, roles, and JWT authentication (see [Users](#users) below) +- **Policy Documents** — structured permission rules attached to memberships and project keys +- **Policy Engine** — evaluation logic that resolves allow/deny decisions at request time + +## Authentication + +SOAT supports two authentication methods. Both use the `Authorization: Bearer ` header. + +### JWT (Users) + +Users authenticate via `POST /api/v1/users/login` with username and password. The server returns a signed JWT containing the user's public ID and role. Admin users bypass policy evaluation and have unrestricted access. Regular users are authorized through their project membership policies. + +### Project Keys + +Project keys are prefixed with `pk_` and scoped to a single project. When an project key is used, authorization applies **intersection semantics**: both the owning user's membership policies _and_ the key's own attached policy must independently allow the action. This ensures project keys can never exceed the permissions of the user who created them. + +## Policy Documents + +A policy document is a JSON object containing one or more statements. Each statement describes a permission rule. + +```json +{ + "statement": [ + { + "effect": "Allow", + "action": ["documents:GetDocument", "documents:ListDocuments"], + "resource": ["soat:proj_ABC:document:doc_XYZ"] + }, + { + "effect": "Deny", + "action": ["secrets:*"], + "resource": ["soat:proj_ABC:secret:sec_PROD_KEY"] + } + ] +} +``` + +### Statement + +| Field | Type | Required | Description | +| ----------- | ---------- | -------- | ------------------------------------------------------- | +| `effect` | `string` | Yes | `"Allow"` or `"Deny"` | +| `action` | `string[]` | Yes | Actions this statement applies to (supports wildcards) | +| `resource` | `string[]` | No | SRNs this statement applies to (default: `["*"]`) | +| `condition` | `object` | No | Conditions that must be true for the statement to apply | + +Policy documents are created and managed under a project via the project policy endpoints (see [Projects](projects.md)). + +## SOAT Resource Names (SRNs) + +Every addressable entity has a canonical identifier called a SOAT Resource Name: + +``` +soat::: +``` + +Examples: + +| SRN | Description | +| -------------------------------- | -------------------------- | +| `soat:proj_ABC:document:doc_XYZ` | A specific document | +| `soat:proj_ABC:document:*` | All documents in a project | +| `soat:proj_ABC:file:*` | All files in a project | +| `soat:proj_ABC:actor:act_123` | A specific actor | +| `soat:*:*:*` | Everything (admin-level) | + +### Resource Types + +| Resource Type | Public ID Prefix | Module | +| -------------- | ---------------- | ------------- | +| `document` | `doc_` | Documents | +| `file` | `file_` | Files | +| `actor` | `act_` | Actors | +| `conversation` | `conv_` | Conversations | +| `project` | `proj_` | Projects | +| `policy` | `pol_` | Policies | +| `api-key` | `key_` | project keys | + +## Actions + +Actions follow the `module:Operation` pattern. Each module defines its own set of actions documented in the **Permissions** section of the respective module page: + +- [Actors Permissions](actors.md#permissions) +- [Conversations Permissions](conversations.md#permissions) +- [Documents Permissions](documents.md#permissions) +- [Files Permissions](files.md#permissions) +- [Projects Permissions](projects.md#permissions) +- [Project Keys Permissions](projects.md#project-key-permissions) +- [Users Permissions](#user-permissions) + +### Wildcards + +- `*` — matches all actions across all modules +- `module:*` — matches all actions in a specific module (e.g., `documents:*`) + +## Conditions + +Conditions add attribute-based constraints to statements. A condition block maps an operator to one or more key-value pairs that must all evaluate to true. + +```json +{ + "condition": { + "StringEquals": { + "soat:ResourceTag/environment": "production" + }, + "StringLike": { + "soat:ResourceTag/team": "engineering-*" + } + } +} +``` + +### Condition Operators + +| Operator | Description | +| ----------------- | ----------------------------- | +| `StringEquals` | Exact string match | +| `StringNotEquals` | Negated exact match | +| `StringLike` | Glob pattern match (`*`, `?`) | + +### Condition Keys + +| Key | Source | Description | +| ------------------------ | ------------- | --------------------------------------- | +| `soat:ResourceTag/` | Resource tags | Tag value on the target resource | +| `soat:ResourceType` | Request | The type of the resource being accessed | + +## Policy Evaluation + +Policy evaluation follows AWS IAM semantics: + +1. **Default deny** — if no statement matches, access is denied. +2. **Explicit deny wins** — if any statement explicitly denies, access is denied regardless of allows. +3. **Allow** — if at least one statement allows and no statement denies, access is granted. + +### Statement Matching + +A statement matches a request when **all** of the following are true: + +1. At least one pattern in `action` matches the requested action. +2. At least one pattern in `resource` matches the target SRN (or `resource` is omitted / `["*"]`). +3. All `condition` blocks evaluate to true (or `condition` is omitted). + +### Pattern Matching + +- `*` matches everything. +- `module:*` matches all actions in a module. +- `soat:proj_ABC:document:*` matches all documents in a project. +- Wildcards apply only at segment boundaries — partial wildcards like `doc_X*` are not supported. + +## Tags + +Tags are key-value pairs attached to resources. They enable attribute-based access control (ABAC) via conditions. Taggable resources include documents, files, actors, and conversations. + +```json +{ + "tags": { + "environment": "production", + "team": "engineering", + "sensitivity": "high" + } +} +``` + +Tags are managed via each resource's create/update endpoints using the `tags` field, or through dedicated tag sub-endpoints: + +``` +PUT /api/v1//:id/tags Replace all tags +PATCH /api/v1//:id/tags Merge tags +GET /api/v1//:id/tags Get tags +``` + +## Examples + +### Full Admin Policy + +Equivalent to unrestricted access: + +```json +{ + "version": "2025-01-01", + "statement": [ + { + "effect": "Allow", + "action": ["*"], + "resource": ["*"] + } + ] +} +``` + +### Read-only Across All Modules + +```json +{ + "version": "2025-01-01", + "statement": [ + { + "effect": "Allow", + "action": [ + "documents:GetDocument", + "documents:ListDocuments", + "documents:SearchDocuments", + "files:GetFile", + "files:DownloadFile", + "actors:ListActors", + "actors:GetActor", + "conversations:ListConversations", + "conversations:GetConversation" + ], + "resource": ["*"] + } + ] +} +``` + +### Allow All File Operations Except Delete + +```json +{ + "version": "2025-01-01", + "statement": [ + { + "effect": "Allow", + "action": ["files:*"], + "resource": ["soat:proj_ABC:file:*"] + }, + { + "effect": "Deny", + "action": ["files:DeleteFile"], + "resource": ["soat:proj_ABC:file:*"] + } + ] +} +``` + +### Condition-based Access + +Allow only actors tagged `"internal"`: + +```json +{ + "version": "2025-01-01", + "statement": [ + { + "effect": "Allow", + "action": ["actors:GetActor"], + "resource": ["soat:proj_ABC:actor:*"], + "condition": { + "StringEquals": { + "soat:ResourceTag/visibility": "internal" + } + } + } + ] +} +``` + +--- + +## Users + +The Users section covers identity management and authentication. Users authenticate via username and password and receive a JWT for subsequent requests. + +A User has a username, a hashed password, and a role (`admin` or `user`). Users are identified by an `id` prefixed with `user_`. Passwords are hashed with bcrypt and never returned in API responses. + +The first user is created via the **bootstrap** endpoint, which is only available when no users exist. Subsequent users are created by admins. + +## Roles + +| Role | Description | +| ------- | --------------------------------------------------------------------------- | +| `admin` | Full access to all resources and operations. Bypasses policy evaluation. | +| `user` | Access determined by project membership policies. Must be a project member. | + +### User Data Model + +| Field | Type | Description | +| ----------- | ------ | --------------------------------------- | +| `id` | string | Public identifier prefixed with `user_` | +| `username` | string | Unique username | +| `role` | string | `"admin"` or `"user"` | +| `createdAt` | string | ISO 8601 creation timestamp | +| `updatedAt` | string | ISO 8601 last-updated timestamp | + +Sensitive fields (`passwordHash`, internal numeric ID) are never exposed in responses. + +### Bootstrap + +The `POST /api/v1/users/bootstrap` endpoint creates the first admin user. It is only available when the user table is empty and returns `409 Conflict` if any user already exists. This endpoint does not require authentication. + +### User Authentication + +Users authenticate with `POST /api/v1/users/login`, providing `username` and `password`. On success, the server returns a signed JWT containing the user's public ID and role. The token is passed as `Authorization: Bearer ` on subsequent requests. + +### User Permissions + +User management is restricted to admin users. These operations are not governed by the policy engine — they require the `admin` role directly. + +| Action | Permission | REST Endpoint | MCP Tool | +| -------------- | --------------- | ------------------------------ | -------- | +| List users | Admin only | `GET /api/v1/users` | — | +| Get user by ID | Admin only | `GET /api/v1/users/:id` | — | +| Create user | Admin only | `POST /api/v1/users` | — | +| Delete user | Admin only | `DELETE /api/v1/users/:id` | — | +| Bootstrap | Unauthenticated | `POST /api/v1/users/bootstrap` | — | +| Login | Unauthenticated | `POST /api/v1/users/login` | — | + +See the [API Reference](../api/users/list-users) for full endpoint details, request/response schemas, and status codes. diff --git a/packages/website/docs/modules/projects.md b/packages/website/docs/modules/projects.md new file mode 100644 index 00000000..594d977c --- /dev/null +++ b/packages/website/docs/modules/projects.md @@ -0,0 +1,181 @@ +# Projects Module + +The Projects module provides multi-tenant namespaces in SOAT. Every resource (document, file, actor, conversation) belongs to a project. Projects also own policy documents, manage user membership, and issue project keys for programmatic access. + +## Overview + +A Project is a top-level container that scopes all resources. Users access projects through membership, and their permissions within a project are determined by attached policy documents. Projects are identified by an `id` prefixed with `proj_`. + +## Data Model + +| Field | Type | Description | +| ----------- | ------ | --------------------------------------- | +| `id` | string | Public identifier prefixed with `proj_` | +| `name` | string | Human-readable project name | +| `createdAt` | string | ISO 8601 creation timestamp | +| `updatedAt` | string | ISO 8601 last-updated timestamp | + +## Key Concepts + +### Membership + +Users are added to projects as members. Each membership associates the user with one or more policy documents that define what the user can do within that project. A user can be a member of multiple projects, each with different policies. + +### Policy Documents + +Policy documents are scoped to a project and contain structured IAM statements. See [IAM Module](iam.md) for the full policy format, evaluation logic, and examples. + +**Policy data model:** + +| Field | Type | Description | +| ------------- | ------ | -------------------------------------- | +| `id` | string | Public identifier prefixed with `pol_` | +| `name` | string | Human-readable label | +| `description` | string | Optional description | +| `document` | object | Policy document (see [IAM](iam.md)) | +| `projectId` | string | ID of the owning project | +| `createdAt` | string | ISO 8601 creation timestamp | +| `updatedAt` | string | ISO 8601 last-updated timestamp | + +### Visibility Rules + +- **Admin users** see all projects. +- **project key callers** are restricted to the project the key is scoped to. +- **Regular users** see only projects they are members of. + +## Permissions + +Project CRUD and management operations are restricted to admin users. The `projects:GetProject` action is used by the policy engine for listing and reading policies as a member. + +| Action | Permission | REST Endpoint | MCP Tool | +| ---------------------- | --------------------- | ---------------------------------------------------------- | --------------- | +| List projects | Authenticated | `GET /api/v1/projects` | `list-projects` | +| Get project by ID | Authenticated | `GET /api/v1/projects/:id` | `get-project` | +| Create project | Admin only | `POST /api/v1/projects` | — | +| Delete project | Admin only | `DELETE /api/v1/projects/:id` | — | +| List policies | `projects:GetProject` | `GET /api/v1/projects/:projectId/policies` | — | +| Get policy | `projects:GetProject` | `GET /api/v1/projects/:projectId/policies/:policyId` | — | +| Create policy | Admin only | `POST /api/v1/projects/:projectId/policies` | — | +| Update policy | Admin only | `PUT /api/v1/projects/:projectId/policies/:policyId` | — | +| Delete policy | Admin only | `DELETE /api/v1/projects/:projectId/policies/:policyId` | — | +| Add member | Admin only | `POST /api/v1/projects/:projectId/members` | — | +| Update member policies | Admin only | `PUT /api/v1/projects/:projectId/members/:userId/policies` | — | +| Get member policies | Admin only | `GET /api/v1/projects/:projectId/members/:userId/policies` | — | + +### Create a Policy + +```http +POST /api/v1/projects/proj_abc123/policies +Authorization: Bearer +Content-Type: application/json + +{ + "name": "Read-only Documents", + "description": "Allows reading all documents", + "document": { + "version": "2025-01-01", + "statement": [ + { + "effect": "Allow", + "action": ["documents:GetDocument", "documents:ListDocuments"], + "resource": ["*"] + } + ] + } +} +``` + +**Response** `201 Created` + +```json +{ + "id": "pol_def456", + "name": "Read-only Documents", + "description": "Allows reading all documents", + "document": { "...": "..." }, + "projectId": "proj_abc123", + "createdAt": "2025-01-01T00:00:00.000Z", + "updatedAt": "2025-01-01T00:00:00.000Z" +} +``` + +### Add a Member to a Project + +```http +POST /api/v1/projects/proj_abc123/members +Authorization: Bearer +Content-Type: application/json + +{ + "userId": "user_def456", + "policyIds": ["pol_def456"] +} +``` + +**Response** `201 Created` + +### Update Member Policies + +```http +PUT /api/v1/projects/proj_abc123/members/user_def456/policies +Authorization: Bearer +Content-Type: application/json + +{ + "policyIds": ["pol_def456", "pol_ghi789"] +} +``` + +**Response** `200 OK` + +--- + +## Project Keys + +Project Keys provide project key-based authentication for programmatic access to SOAT. Each key is scoped to a single project and bound to a single policy document. The raw key is returned only once at creation time — it cannot be retrieved afterwards. + +Project Keys are identified by an `id` prefixed with `key_`. + +### Project Key Data Model + +| Field | Type | Description | +| ----------- | ------ | ---------------------------------------------- | +| `id` | string | Public identifier prefixed with `key_` | +| `name` | string | Human-readable label | +| `keyPrefix` | string | First 8 characters of the raw key (for lookup) | +| `userId` | string | Public ID of the user who created the key | +| `projectId` | string | Public ID of the project the key is scoped to | +| `policyId` | string | Public ID of the attached policy | +| `createdAt` | string | ISO 8601 creation timestamp | +| `updatedAt` | string | ISO 8601 last-updated timestamp | + +The raw secret key is only returned in the `POST` response. Only `keyPrefix` and a bcrypt hash are stored. + +### Security Model + +- The raw key is a 32-byte random value prefixed with `pk_`. +- Only the `keyPrefix` (first 8 characters) and a bcrypt hash of the full key are stored. +- Authentication works by matching the prefix to candidate rows, then verifying the full key against each hash. + +### Intersection Authorization + +When an project key is used to make a request, authorization applies **intersection semantics**: + +1. The owning user's project membership policies must allow the action. +2. The key's own attached policy must also allow the action. + +Both must independently evaluate to `Allow`. This ensures a key can never exceed the permissions of the user who created it. + +### Scoping + +A project key is scoped to exactly one project. Requests made with the key can only access resources within that project. The project is resolved automatically from the key — callers do not need to specify the project explicitly. + +### Project Key Permissions + +Project key operations require authentication. The creator of a key is the only user who can read or update it (ownership enforcement). + +| Action | Permission | REST Endpoint | MCP Tool | +| ----------------- | -------------- | ------------------------------ | -------- | +| Create key | Project member | `POST /api/v1/project-keys` | — | +| Get key by ID | Key owner only | `GET /api/v1/project-keys/:id` | — | +| Update key policy | Key owner only | `PUT /api/v1/project-keys/:id` | — | diff --git a/packages/website/docs/modules/secrets.md b/packages/website/docs/modules/secrets.md new file mode 100644 index 00000000..b4da5f43 --- /dev/null +++ b/packages/website/docs/modules/secrets.md @@ -0,0 +1,46 @@ +# Secrets Module + +The Secrets module provides encrypted storage for sensitive values such as API keys and credentials. Values are encrypted at rest using AES-256-GCM and are never returned by any API response. + +## Overview + +Secrets are associated with a project. Once stored, a secret's value can only be replaced — it is never readable again. All operations return a `hasValue` boolean to indicate whether an encrypted value is on file. + +Secrets can be linked to [AI Providers](./ai-providers.md) to supply credentials at inference time. + +## Configuration + +| Environment Variable | Required | Description | +| ------------------------ | -------- | ------------------------------------------------------------------------------------------------ | +| `SECRETS_ENCRYPTION_KEY` | Yes | 64-character hex string (32 bytes). Used for AES-256-GCM encryption of all stored secret values. | + +Generate a key with: + +```bash +openssl rand -hex 32 +``` + +## Data Model + +| Field | Type | Description | +| ----------- | ------- | ---------------------------------------- | +| `id` | string | Public identifier (e.g. `sec_…`) | +| `projectId` | string | ID of the owning project | +| `name` | string | Human-readable label | +| `hasValue` | boolean | `true` when an encrypted value is stored | +| `createdAt` | string | ISO 8601 creation timestamp | +| `updatedAt` | string | ISO 8601 last-updated timestamp | + +## Deletion behaviour + +By default, deleting a secret that is still referenced by one or more AI providers returns `409 Conflict`. Pass `?force=true` to cascade-delete the dependent AI providers along with the secret. + +## Permissions + +| Action | Permission | REST Endpoint | MCP Tool | +| ------------- | ---------------------- | ---------------------------------- | --------------- | +| List secrets | `secrets:ListSecrets` | `GET /api/v1/secrets` | `list-secrets` | +| Get a secret | `secrets:GetSecret` | `GET /api/v1/secrets/:secretId` | `get-secret` | +| Create secret | `secrets:CreateSecret` | `POST /api/v1/secrets` | `create-secret` | +| Update secret | `secrets:UpdateSecret` | `PATCH /api/v1/secrets/:secretId` | `update-secret` | +| Delete secret | `secrets:DeleteSecret` | `DELETE /api/v1/secrets/:secretId` | `delete-secret` | diff --git a/packages/website/docs/tutorials/connect-mcp.md b/packages/website/docs/tutorials/connect-mcp.md deleted file mode 100644 index 6b82ec61..00000000 --- a/packages/website/docs/tutorials/connect-mcp.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Connect with MCP - -The **Model Context Protocol (MCP)** is the easiest way to consume SOAT. It allows AI clients (like Claude Desktop) to automatically discover and use the memory tools provided by your server. - -## Integrating with Claude Desktop - -1. **Locate your Claude Config** - - Find the `claude_desktop_config.json` file on your machine: - - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` - - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` - -2. **Add SOAT Server** - - Add the following configuration to the `mcpServers` object. We use `stdio` (standard input/output) for local development or `sse` (Server-Sent Events) for remote servers. - - _If you are running the server locally via Docker on port 3000, you will likely use the SSE transport._ - - ```json - { - "mcpServers": { - "soat": { - "command": "node", - "args": ["path/to/soat/packages/server/dist/index.js"], - "env": { - "DATABASE_URL": "postgres://soat_user:soat_password@localhost:5432/soat_db" - } - } - } - } - ``` - - > **Wait!** The example above assumes you are running the MCP server directly via `node`. - > If you are using the **Docker container** we set up in "Getting Started", you need an MCP Client that supports HTTP/SSE. - > - > _Currently, Claude Desktop creates a local process. You might need to run a local "bridge" script or run the node process directly as shown above._ - - **Recommended for Local Source Usage:** - If you cloned the repo, the easiest way currently is to point Claude directly to the built server file: - - ```bash - # First, ensure you have built the project - pnpm install - pnpm build - ``` - - Then update your config to point to the absolute path of the built file. - -3. **Restart Claude** - - Restart the Claude Desktop application. You should see a 🔌 icon indicating the MCP server is connected. - -4. **Test It** - - Ask Claude: _"Please save this conversation to my memory"_ or _"What do you know about my project SOAT?"_. - -## Using with Cursor - -Cursor also supports MCP. Go to **Cursor Settings > Features > MCP** and add a new server. diff --git a/packages/website/docs/tutorials/storing-memory.md b/packages/website/docs/tutorials/storing-memory.md deleted file mode 100644 index 9d5fe3e4..00000000 --- a/packages/website/docs/tutorials/storing-memory.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -sidebar_position: 2 ---- - -# Storing & Retrieving Memory - -Once connected via MCP, your agent has access to specific tools to interact with the memory database. - -## Available Tools - -The SOAT Server exposes the following tools to the agent: - -### `add_memory` - -Stores a piece of text into the database. It automatically generates a vector embedding for semantic search later. - -- **Input**: `text` (string), `metadata` (optional JSON) -- **Example**: "The user prefers TypeScript over JavaScript for all new projects." - -### `search_memory` - -Retrieves relevant memories based on a query. It compares the vector embedding of the query with stored memories. - -- **Input**: `query` (string) -- **Example**: "What are the user's coding preferences?" -- **Output**: A list of matching text snippets with similarity scores. - -## Example Workflow - -Here is how an interaction might look between you and an agent using SOAT: - -1. **User**: "My API key for the weather service is `12345`. Remember that." -2. **Agent**: _Calls `add_memory` with text "Weather service API key is 12345"._ -3. **Agent**: "I have stored that API key in your memory." - -... _Days later_ ... - -1. **User**: "I need to check the weather, what credential should I use?" -2. **Agent**: _Calls `search_memory` with "weather credential API key"._ -3. **Soat Server**: _Returns the memory stored earlier._ -4. **Agent**: "You should use the API key `12345`." - -## Best Practices - -- **Be Specific**: Agents perform best when memories are atomic and self-contained. -- **Use Context**: When asking the agent to remember something, explicitly say "save this to memory". diff --git a/packages/website/docusaurus.config.ts b/packages/website/docusaurus.config.ts index a1b99968..918a5b4a 100644 --- a/packages/website/docusaurus.config.ts +++ b/packages/website/docusaurus.config.ts @@ -1,9 +1,34 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; + import type * as Preset from '@docusaurus/preset-classic'; import type { Config } from '@docusaurus/types'; import { themes as prismThemes } from 'prism-react-renderer'; // This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) +const buildOpenApiConfig = () => { + const specsDir = path.resolve(__dirname, '../server/src/rest/openapi/v1'); + const files = fs.readdirSync(specsDir).filter((f) => { + return f.endsWith('.yaml'); + }); + return Object.fromEntries( + files.map((file) => { + const name = path.basename(file, '.yaml'); + return [ + name, + { + specPath: `../server/src/rest/openapi/v1/${file}`, + outputDir: `docs/api/${name}`, + sidebarOptions: { groupPathsBy: 'tag' }, + hideSendButton: false, + showInfoPage: false, + }, + ]; + }) + ); +}; + const config: Config = { title: 'SOAT', tagline: 'Persistent Memory for AI Agents', @@ -73,24 +98,7 @@ const config: Config = { { id: 'api', docsPluginId: 'classic', - config: { - files: { - specPath: '../server/src/rest/openapi/v1/files.yaml', - outputDir: 'docs/api/files', - sidebarOptions: { - groupPathsBy: 'tag', - }, - hideSendButton: false, - }, - documents: { - specPath: '../server/src/rest/openapi/v1/documents.yaml', - outputDir: 'docs/api/documents', - sidebarOptions: { - groupPathsBy: 'tag', - }, - hideSendButton: false, - }, - }, + config: buildOpenApiConfig(), }, ], ], @@ -113,16 +121,16 @@ const config: Config = { { type: 'docSidebar', sidebarId: 'tutorialSidebar', - position: 'right', + position: 'left', label: 'Docs', }, { type: 'docSidebar', sidebarId: 'apiSidebar', - position: 'right', + position: 'left', label: 'API', }, - { to: '/blog', label: 'Blog', position: 'right' }, + { to: '/blog', label: 'Blog', position: 'left' }, { href: 'https://github.com/ttoss/soat', label: 'GitHub', @@ -138,7 +146,7 @@ const config: Config = { items: [ { label: 'Documentation', - to: '/docs/intro', + to: '/docs/Introduction', }, ], }, diff --git a/packages/website/package.json b/packages/website/package.json index 3cd721ef..d3dda81c 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -5,9 +5,10 @@ "scripts": { "docusaurus": "docusaurus", "dev": "docusaurus start", - "build": "docusaurus build", + "build": "docusaurus gen-api-docs all && docusaurus build", "swizzle": "docusaurus swizzle", - "deploy": "docusaurus deploy", + "deploy": "carlin deploy static-app", + "deploy-report": "carlin deploy report --channel=github-pr", "clear": "docusaurus clear", "serve": "docusaurus serve", "write-translations": "docusaurus write-translations", @@ -17,21 +18,22 @@ "clean-api-docs": "docusaurus clean-api-docs all" }, "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/preset-classic": "3.9.2", - "@mdx-js/react": "^3.0.0", - "clsx": "^2.0.0", - "prism-react-renderer": "^2.3.0", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "docusaurus-plugin-openapi-docs": "^4.0.0", - "docusaurus-theme-openapi-docs": "^4.0.0" + "@docusaurus/core": "3.10.0", + "@docusaurus/faster": "^3.10.0", + "@docusaurus/preset-classic": "3.10.0", + "@mdx-js/react": "^3.1.1", + "clsx": "^2.1.1", + "docusaurus-plugin-openapi-docs": "^5.0.0", + "docusaurus-theme-openapi-docs": "^5.0.0", + "prism-react-renderer": "^2.4.1", + "react": "^19.2.5", + "react-dom": "^19.2.5" }, "devDependencies": { - "@docusaurus/module-type-aliases": "3.9.2", - "@docusaurus/tsconfig": "3.9.2", - "@docusaurus/types": "3.9.2", - "typescript": "~5.9.3" + "@docusaurus/module-type-aliases": "3.10.0", + "@docusaurus/tsconfig": "3.10.0", + "@docusaurus/types": "3.10.0", + "typescript": "~6.0.2" }, "browserslist": { "production": [ diff --git a/packages/website/sidebars.ts b/packages/website/sidebars.ts index 6122652b..d743ae6d 100644 --- a/packages/website/sidebars.ts +++ b/packages/website/sidebars.ts @@ -1,8 +1,5 @@ import type { SidebarsConfig } from '@docusaurus/plugin-content-docs'; -import documentsSidebar from './docs/api/documents/sidebar'; -import filesSidebar from './docs/api/files/sidebar'; - // This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) /** @@ -18,33 +15,17 @@ import filesSidebar from './docs/api/files/sidebar'; const sidebars: SidebarsConfig = { // By default, Docusaurus generates a sidebar from the docs folder structure tutorialSidebar: [ - 'intro', + 'Introduction', 'getting-started', - { - type: 'category', - label: 'Tutorials', - items: [{ type: 'autogenerated', dirName: 'tutorials' }], - }, { type: 'category', label: 'Modules', - items: ['modules/files'], + items: [{ type: 'autogenerated', dirName: 'modules' }], }, ], // API sidebar - combines the generated OpenAPI sidebars - apiSidebar: [ - { - type: 'category', - label: 'Documents API', - items: documentsSidebar, - }, - { - type: 'category', - label: 'Files API', - items: filesSidebar, - }, - ], + apiSidebar: [{ type: 'autogenerated', dirName: 'api' }], // But you can create a sidebar manually /* diff --git a/packages/website/tsconfig.json b/packages/website/tsconfig.json index 920d7a65..ae701a6d 100644 --- a/packages/website/tsconfig.json +++ b/packages/website/tsconfig.json @@ -2,7 +2,8 @@ // This file is not used in compilation. It is here just for a nice editor experience. "extends": "@docusaurus/tsconfig", "compilerOptions": { - "baseUrl": "." + "baseUrl": ".", + "ignoreDeprecations": "6.0" }, "exclude": [".docusaurus", "build"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e97ae543..4da84e58 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,32 +9,35 @@ importers: .: devDependencies: '@commitlint/cli': - specifier: ^20.1.0 - version: 20.2.0(@types/node@25.0.3)(typescript@5.9.3) + specifier: ^20.5.0 + version: 20.5.0(@types/node@25.5.2)(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)(typescript@6.0.2) '@lerna-lite/changed': - specifier: ^4.9.4 - version: 4.10.2(@lerna-lite/version@4.10.2)(@types/node@25.0.3) + specifier: ^5.0.0 + version: 5.0.0(@lerna-lite/version@5.0.0)(@types/node@25.5.2) '@lerna-lite/cli': - specifier: ^4.9.4 - version: 4.10.2(@lerna-lite/list@4.10.2)(@lerna-lite/version@4.10.2)(@types/node@25.0.3) + specifier: ^5.0.0 + version: 5.0.0(@lerna-lite/list@5.0.0)(@lerna-lite/version@5.0.0)(@types/node@25.5.2) '@lerna-lite/list': - specifier: ^4.9.4 - version: 4.10.2(@lerna-lite/version@4.10.2)(@types/node@25.0.3) + specifier: ^5.0.0 + version: 5.0.0(@lerna-lite/version@5.0.0)(@types/node@25.5.2) '@lerna-lite/version': - specifier: ^4.9.4 - version: 4.10.2(@lerna-lite/list@4.10.2)(@types/node@25.0.3)(conventional-commits-filter@5.0.0) + specifier: ^5.0.0 + version: 5.0.0(@lerna-lite/list@5.0.0)(@types/node@25.5.2)(conventional-commits-filter@5.0.0) '@ttoss/config': - specifier: ^1.35.12 - version: 1.35.12 + specifier: ^1.37.8 + version: 1.37.8 '@ttoss/eslint-config': - specifier: ^1.26.6 - version: 1.26.6(@testing-library/dom@10.4.1)(@types/eslint@9.6.1)(@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(jest@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)))(prettier@3.7.4)(turbo@2.6.3)(typescript@5.9.3) + specifier: ^1.26.14 + version: 1.26.14(@testing-library/dom@10.4.1)(@types/eslint@9.6.1)(@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2))(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2))(eslint@9.39.2(jiti@2.6.1))(jest@30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2)))(prettier@3.8.1)(turbo@2.9.4)(typescript@6.0.2) '@ttoss/monorepo': - specifier: ^1.28.0 - version: 1.28.0 + specifier: ^1.29.8 + version: 1.29.8 '@types/node': - specifier: ^25.0.3 - version: 25.0.3 + specifier: ^25.5.2 + version: 25.5.2 + carlin: + specifier: ^1.48.3 + version: 1.48.3(@swc/core@1.15.24)(@types/node@25.5.2)(encoding@0.1.13)(rollup@4.53.3)(typescript@6.0.2) eslint: specifier: ^9.39.1 version: 9.39.2(jiti@2.6.1) @@ -42,20 +45,20 @@ importers: specifier: ^9.1.7 version: 9.1.7 lint-staged: - specifier: ^16.2.7 - version: 16.2.7 + specifier: ^16.4.0 + version: 16.4.0 prettier: - specifier: ^3.7.4 - version: 3.7.4 + specifier: ^3.8.1 + version: 3.8.1 syncpack: - specifier: 13.0.4 - version: 13.0.4(typescript@5.9.3) + specifier: ^14.3.0 + version: 14.3.0 turbo: - specifier: ^2.6.2 - version: 2.6.3 + specifier: ^2.9.4 + version: 2.9.4 typescript: - specifier: ~5.9.3 - version: 5.9.3 + specifier: ~6.0.2 + version: 6.0.2 packages/cli: dependencies: @@ -67,236 +70,118 @@ importers: version: 14.0.2 devDependencies: '@ttoss/config': - specifier: ^1.35.12 - version: 1.35.12 + specifier: ^1.37.8 + version: 1.37.8 '@types/jest': specifier: ^30.0.0 version: 30.0.0 jest: - specifier: ^30.2.0 - version: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) + specifier: ^30.3.0 + version: 30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2)) tsup: specifier: ^8.5.1 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(@swc/core@1.15.24)(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) tsx: specifier: ^4.21.0 version: 4.21.0 - packages/documents-core: - dependencies: - '@soat/embeddings-core': - specifier: workspace:* - version: link:../embeddings-core - '@soat/files-core': - specifier: workspace:* - version: link:../files-core - '@soat/postgresdb': - specifier: workspace:* - version: link:../postgresdb - '@ttoss/postgresdb': - specifier: ^0.3.0 - version: 0.3.0(@types/node@25.0.3)(@types/validator@13.15.10)(reflect-metadata@0.2.2) - uuid: - specifier: ^10.0.0 - version: 10.0.0 - devDependencies: - '@ttoss/config': - specifier: ^1.35.12 - version: 1.35.12 - '@types/jest': - specifier: ^30.0.0 - version: 30.0.0 - '@types/node': - specifier: ^25.0.3 - version: 25.0.3 - '@types/uuid': - specifier: ^10.0.0 - version: 10.0.0 - jest: - specifier: ^30.2.0 - version: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) - tsup: - specifier: ^8.5.1 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) - tsx: - specifier: ^4.21.0 - version: 4.21.0 - - packages/embeddings-core: - dependencies: - ollama: - specifier: ^0.6.3 - version: 0.6.3 - devDependencies: - '@ttoss/config': - specifier: ^1.35.12 - version: 1.35.12 - '@types/node': - specifier: ^25.0.3 - version: 25.0.3 - jest: - specifier: ^30.2.0 - version: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) - tsup: - specifier: ^8.5.1 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) - typescript: - specifier: ~5.9.3 - version: 5.9.3 - - packages/files-core: - dependencies: - '@google-cloud/storage': - specifier: ^7.0.0 - version: 7.18.0(encoding@0.1.13) - '@soat/postgresdb': - specifier: workspace:* - version: link:../postgresdb - '@ttoss/postgresdb': - specifier: ^0.3.0 - version: 0.3.0(@types/node@25.0.3)(@types/validator@13.15.10)(reflect-metadata@0.2.2) - aws-sdk: - specifier: ^2.0.0 - version: 2.1693.0 - uuid: - specifier: ^10.0.0 - version: 10.0.0 - devDependencies: - '@ttoss/config': - specifier: ^1.35.12 - version: 1.35.12 - '@types/node': - specifier: ^25.0.3 - version: 25.0.3 - '@types/uuid': - specifier: ^10.0.0 - version: 10.0.0 - jest: - specifier: ^30.2.0 - version: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) - tsup: - specifier: ^8.5.1 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) - typescript: - specifier: ~5.9.3 - version: 5.9.3 - packages/postgresdb: dependencies: '@ttoss/postgresdb': - specifier: ^0.3.0 - version: 0.3.0(@types/node@25.0.3)(@types/validator@13.15.10)(reflect-metadata@0.2.2) + specifier: ^0.8.0 + version: 0.8.0(@types/node@25.5.2)(@types/validator@13.15.10)(reflect-metadata@0.2.2) + nanoid: + specifier: ^5.1.7 + version: 5.1.7 devDependencies: '@testcontainers/postgresql': - specifier: ^11.11.0 - version: 11.11.0 + specifier: ^11.13.0 + version: 11.13.0 '@ttoss/config': - specifier: ^1.35.12 - version: 1.35.12 + specifier: ^1.37.8 + version: 1.37.8 '@ttoss/postgresdb-cli': - specifier: ^0.1.24 - version: 0.1.24 + specifier: ^0.2.8 + version: 0.2.8 '@ttoss/test-utils': - specifier: ^4.0.2 - version: 4.0.2(@types/jest@30.0.0)(@types/react@19.2.7)(encoding@0.1.13)(jest@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@types/jest': - specifier: ^30.0.0 - version: 30.0.0 + specifier: ^4.2.8 + version: 4.2.8(@types/jest@30.0.0)(@types/react@19.2.7)(encoding@0.1.13)(jest@30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2)))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@types/node': - specifier: ^25.0.3 - version: 25.0.3 - jest: - specifier: ^30.2.0 - version: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) + specifier: ^25.5.2 + version: 25.5.2 tsup: specifier: ^8.5.1 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(@swc/core@1.15.24)(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) typescript: - specifier: ~5.9.3 - version: 5.9.3 + specifier: ~6.0.2 + version: 6.0.2 packages/server: dependencies: - '@soat/documents-core': - specifier: workspace:* - version: link:../documents-core - '@soat/embeddings-core': - specifier: workspace:* - version: link:../embeddings-core - '@soat/files-core': - specifier: workspace:* - version: link:../files-core '@soat/postgresdb': specifier: workspace:* version: link:../postgresdb '@ttoss/http-server': - specifier: ^0.3.2 - version: 0.3.2 + specifier: ^0.5.9 + version: 0.5.9 '@ttoss/http-server-mcp': - specifier: ^0.3.2 - version: 0.3.2 + specifier: ^0.11.1 + version: 0.11.1 '@ttoss/postgresdb': - specifier: ^0.3.0 - version: 0.3.0(@types/node@25.0.3)(@types/validator@13.15.10)(reflect-metadata@0.2.2) + specifier: ^0.8.0 + version: 0.8.0(@types/node@25.5.2)(@types/validator@13.15.10)(reflect-metadata@0.2.2) + bcryptjs: + specifier: ^3.0.3 + version: 3.0.3 dotenv: - specifier: ^17.2.3 - version: 17.2.3 + specifier: ^17.4.1 + version: 17.4.1 + jsonwebtoken: + specifier: ^9.0.3 + version: 9.0.3 ollama: specifier: ^0.6.3 version: 0.6.3 pg: - specifier: ^8.16.3 - version: 8.16.3 + specifier: ^8.20.0 + version: 8.20.0 devDependencies: '@stoplight/spectral-cli': specifier: ^6.15.0 version: 6.15.0(encoding@0.1.13) + '@testcontainers/postgresql': + specifier: ^11.13.0 + version: 11.13.0 '@ttoss/config': - specifier: ^1.35.12 - version: 1.35.12 + specifier: ^1.37.8 + version: 1.37.8 '@ttoss/test-utils': - specifier: ^4.0.2 - version: 4.0.2(@types/jest@30.0.0)(@types/react@19.2.7)(encoding@0.1.13)(jest@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^4.2.8 + version: 4.2.8(@types/jest@30.0.0)(@types/react@19.2.7)(encoding@0.1.13)(jest@30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2)))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@types/bcryptjs': + specifier: ^3.0.0 + version: 3.0.0 '@types/jest': specifier: ^30.0.0 version: 30.0.0 + '@types/jsonwebtoken': + specifier: ^9.0.10 + version: 9.0.10 '@types/pg': - specifier: ^8.16.0 - version: 8.16.0 + specifier: ^8.20.0 + version: 8.20.0 '@types/supertest': - specifier: ^6.0.3 - version: 6.0.3 + specifier: ^7.2.0 + version: 7.2.0 jest: - specifier: ^30.2.0 - version: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) + specifier: ^30.3.0 + version: 30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2)) supertest: specifier: ^7.2.2 version: 7.2.2 tsup: specifier: ^8.5.1 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) - tsx: - specifier: ^4.21.0 - version: 4.21.0 - - packages/text-atomizer: - dependencies: - ollama: - specifier: ^0.6.3 - version: 0.6.3 - devDependencies: - '@ttoss/config': - specifier: ^1.35.12 - version: 1.35.12 - '@types/jest': - specifier: ^30.0.0 - version: 30.0.0 - jest: - specifier: ^30.2.0 - version: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) - tsup: - specifier: ^8.5.1 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(@swc/core@1.15.24)(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3) tsx: specifier: ^4.21.0 version: 4.21.0 @@ -304,45 +189,48 @@ importers: packages/website: dependencies: '@docusaurus/core': - specifier: 3.9.2 - version: 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + specifier: 3.10.0 + version: 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/faster': + specifier: ^3.10.0 + version: 3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) '@docusaurus/preset-classic': - specifier: 3.9.2 - version: 3.9.2(@algolia/client-search@5.46.2)(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(search-insights@2.17.3)(typescript@5.9.3) + specifier: 3.10.0 + version: 3.10.0(@algolia/client-search@5.46.2)(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(@types/react@19.2.7)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(search-insights@2.17.3)(typescript@6.0.2) '@mdx-js/react': - specifier: ^3.0.0 - version: 3.1.1(@types/react@19.2.7)(react@19.2.3) + specifier: ^3.1.1 + version: 3.1.1(@types/react@19.2.7)(react@19.2.5) clsx: - specifier: ^2.0.0 + specifier: ^2.1.1 version: 2.1.1 docusaurus-plugin-openapi-docs: - specifier: ^4.0.0 - version: 4.5.1(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(@docusaurus/utils-validation@3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@docusaurus/utils@3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(encoding@0.1.13)(react@19.2.3) + specifier: ^5.0.0 + version: 5.0.0(@docusaurus/plugin-content-docs@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2))(@docusaurus/utils-validation@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@docusaurus/utils@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/json-schema@7.0.15)(encoding@0.1.13)(react@19.2.5) docusaurus-theme-openapi-docs: - specifier: ^4.0.0 - version: 4.5.1(@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(docusaurus-plugin-openapi-docs@4.5.1(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(@docusaurus/utils-validation@3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@docusaurus/utils@3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(encoding@0.1.13)(react@19.2.3))(docusaurus-plugin-sass@0.2.6(@docusaurus/core@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(sass@1.97.2)(webpack@5.104.1))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1) + specifier: ^5.0.0 + version: 5.0.0(255c652d32e779b619b35f7801205cba) prism-react-renderer: - specifier: ^2.3.0 - version: 2.4.1(react@19.2.3) + specifier: ^2.4.1 + version: 2.4.1(react@19.2.5) react: - specifier: ^19.0.0 - version: 19.2.3 + specifier: ^19.2.5 + version: 19.2.5 react-dom: - specifier: ^19.0.0 - version: 19.2.3(react@19.2.3) + specifier: ^19.2.5 + version: 19.2.5(react@19.2.5) devDependencies: '@docusaurus/module-type-aliases': - specifier: 3.9.2 - version: 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: 3.10.0 + version: 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@docusaurus/tsconfig': - specifier: 3.9.2 - version: 3.9.2 + specifier: 3.10.0 + version: 3.10.0 '@docusaurus/types': - specifier: 3.9.2 - version: 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: 3.10.0 + version: 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) typescript: - specifier: ~5.9.3 - version: 5.9.3 + specifier: ~6.0.2 + version: 6.0.2 packages: @@ -448,9 +336,11 @@ packages: resolution: {integrity: sha512-ciPihkletp7ttweJ8Zt+GukSVLp2ANJHU+9ttiSxsJZThXc4Y2yJ8HGVWesW5jN1zrsZsezN71KrMx/iZsOYpg==} engines: {node: '>= 14.0.0'} - '@apidevtools/json-schema-ref-parser@11.9.3': - resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==} - engines: {node: '>= 16'} + '@apidevtools/json-schema-ref-parser@15.3.5': + resolution: {integrity: sha512-orNOYXw3hYXxxisXMldjzjBzqqTLBPbwOtHg7ovBPvfBHDue1qM9YJENZ3W2BQuS+7z4ThogMbEzEsov57Itkg==} + engines: {node: '>=20'} + peerDependencies: + '@types/json-schema': ^7.0.15 '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} @@ -458,22 +348,207 @@ packages: '@asyncapi/specs@6.10.0': resolution: {integrity: sha512-vB5oKLsdrLUORIZ5BXortZTlVyGWWMC1Nud/0LtgxQ3Yn2738HigAD6EVqScvpPsDUI/bcLVsYEXN4dtXQHVng==} + '@aws-crypto/crc32@5.2.0': + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/crc32c@5.2.0': + resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} + + '@aws-crypto/sha1-browser@5.2.0': + resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} + + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/client-cloudformation@3.1029.0': + resolution: {integrity: sha512-Tr4W2pQ9XDaYRrgrmGeNWVV3xVZ3hovxKgu33pr4oyJWBOXOOP/002QZgkER3mxncx//ESPXyTMTX1JpP3r2+Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-s3@3.1029.0': + resolution: {integrity: sha512-OuA8RZTxsAaHDcI25j2NGLMaYFI2WpJdDzK3uLmVBmaHwjQKQZOUDVVBcln8pNo3IgkY+HRSJhRR4/xlM//UyQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.973.27': + resolution: {integrity: sha512-CUZ5m8hwMCH6OYI4Li/WgMfIEx10Q2PLI9Y3XOUTPGZJ53aZ0007jCv+X/ywsaERyKPdw5MRZWk877roQksQ4A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/crc64-nvme@3.972.6': + resolution: {integrity: sha512-NMbiqKdruhwwgI6nzBVe2jWMkXjaoQz2YOs3rFX+2F3gGyrJDkDPwMpV/RsTFeq2vAQ055wZNtOXFK4NYSkM8g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-env@3.972.25': + resolution: {integrity: sha512-6QfI0wv4jpG5CrdO/AO0JfZ2ux+tKwJPrUwmvxXF50vI5KIypKVGNF6b4vlkYEnKumDTI1NX2zUBi8JoU5QU3A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.27': + resolution: {integrity: sha512-3V3Usj9Gs93h865DqN4M2NWJhC5kXU9BvZskfN3+69omuYlE3TZxOEcVQtBGLOloJB7BVfJKXVLqeNhOzHqSlQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.972.29': + resolution: {integrity: sha512-SiBuAnXecCbT/OpAf3vqyI/AVE3mTaYr9ShXLybxZiPLBiPCCOIWSGAtYYGQWMRvobBTiqOewaB+wcgMMZI2Aw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.29': + resolution: {integrity: sha512-OGOslTbOlxXexKMqhxCEbBQbUIfuhGxU5UXw3Fm56ypXHvrXH4aTt/xb5Y884LOoteP1QST1lVZzHfcTnWhiPQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.30': + resolution: {integrity: sha512-FMnAnWxc8PG+ZrZ2OBKzY4luCUJhe9CG0B9YwYr4pzrYGLXBS2rl+UoUvjGbAwiptxRL6hyA3lFn03Bv1TLqTw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.25': + resolution: {integrity: sha512-HR7ynNRdNhNsdVCOCegy1HsfsRzozCOPtD3RzzT1JouuaHobWyRfJzCBue/3jP7gECHt+kQyZUvwg/cYLWurNQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.972.29': + resolution: {integrity: sha512-HWv4SEq3jZDYPlwryZVef97+U8CxxRos5mK8sgGO1dQaFZpV5giZLzqGE5hkDmh2csYcBO2uf5XHjPTpZcJlig==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.29': + resolution: {integrity: sha512-PdMBza1WEKEUPFEmMGCfnU2RYCz9MskU2e8JxjyUOsMKku7j9YaDKvbDi2dzC0ihFoM6ods2SbhfAAro+Gwlew==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/lib-storage@3.1029.0': + resolution: {integrity: sha512-Un9PaYUUHKCbO+A2w4Hse7Fahg9siRSilR7MR5Eu9wbgzG79fCc/Jgm8x7q35KhRMW2z5Iy4tF5NjSw4nxnksA==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@aws-sdk/client-s3': ^3.1029.0 + + '@aws-sdk/middleware-bucket-endpoint@3.972.9': + resolution: {integrity: sha512-COToYKgquDyligbcAep7ygs48RK+mwe/IYprq4+TSrVFzNOYmzWvHf6werpnKV5VYpRiwdn+Wa5ZXkPqLVwcTg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-expect-continue@3.972.9': + resolution: {integrity: sha512-V/FNCjFxnh4VGu+HdSiW4Yg5GELihA1MIDSAdsEPvuayXBVmr0Jaa6jdLAZLH38KYXl/vVjri9DQJWnTAujHEA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-flexible-checksums@3.974.7': + resolution: {integrity: sha512-uU4/ch2CLHB8Phu1oTKnnQ4e8Ujqi49zEnQYBhWYT53zfFvtJCdGsaOoypBr8Fm/pmCBssRmGoIQ4sixgdLP9w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-host-header@3.972.9': + resolution: {integrity: sha512-je5vRdNw4SkuTnmRbFZLdye4sQ0faLt8kwka5wnnSU30q1mHO4X+idGEJOOE+Tn1ME7Oryn05xxkDvIb3UaLaQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-location-constraint@3.972.9': + resolution: {integrity: sha512-TyfOi2XNdOZpNKeTJwRUsVAGa+14nkyMb2VVGG+eDgcWG/ed6+NUo72N3hT6QJioxym80NSinErD+LBRF0Ir1w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-logger@3.972.9': + resolution: {integrity: sha512-HsVgDrruhqI28RkaXALm8grJ7Agc1wF6Et0xh6pom8NdO2VdO/SD9U/tPwUjewwK/pVoka+EShBxyCvgsPCtog==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-recursion-detection@3.972.10': + resolution: {integrity: sha512-RVQQbq5orQ/GHUnXvqEOj2HHPBJm+mM+ySwZKS5UaLBwra5ugRtiH09PLUoOZRl7a1YzaOzXSuGbn9iD5j60WQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.972.28': + resolution: {integrity: sha512-qJHcJQH9UNPUrnPlRtCozKjtqAaypQ5IgQxTNoPsVYIQeuwNIA8Rwt3NvGij1vCDYDfCmZaPLpnJEHlZXeFqmg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-ssec@3.972.9': + resolution: {integrity: sha512-wSA2BR7L0CyBNDJeSrleIIzC+DzL93YNTdfU0KPGLiocK6YsRv1nPAzPF+BFSdcs0Qa5ku5Kcf4KvQcWwKGenQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-user-agent@3.972.29': + resolution: {integrity: sha512-f/sIRzuTfEjg6NsbMYvye2VsmnQoNgntntleQyx5uGacUYzszbfIlO3GcI6G6daWUmTm0IDZc11qMHWwF0o0mQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/nested-clients@3.996.19': + resolution: {integrity: sha512-uFkmCDXvmQYLanlYdOFS0+MQWkrj9wPMt/ZCc/0J0fjPim6F5jBVBmEomvGY/j77ILW6GTPwN22Jc174Mhkw6Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/region-config-resolver@3.972.11': + resolution: {integrity: sha512-6Q8B1dcx6BBqUTY1Mc/eROKA0FImEEY5VPSd6AGPEUf0ErjExz4snVqa9kNJSoVDV1rKaNf3qrWojgcKW+SdDg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.996.16': + resolution: {integrity: sha512-EMdXYB4r/k5RWq86fugjRhid5JA+Z6MpS7n4sij4u5/C+STrkvuf9aFu41rJA9MjUzxCLzv8U2XL8cH2GSRYpQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1026.0': + resolution: {integrity: sha512-Ieq/HiRrbEtrYP387Nes0XlR7H1pJiJOZKv+QyQzMYpvTiDs0VKy2ZB3E2Zf+aFovWmeE7lRE4lXyF7dYM6GgA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.973.7': + resolution: {integrity: sha512-reXRwoJ6CfChoqAsBszUYajAF8Z2LRE+CRcKocvFSMpIiLOtYU3aJ9trmn6VVPAzbbY5LXF+FfmUslbXk1SYFg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-arn-parser@3.972.3': + resolution: {integrity: sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-endpoints@3.996.6': + resolution: {integrity: sha512-2nUQ+2ih7CShuKHpGSIYvvAIOHy52dOZguYG36zptBukhw6iFwcvGfG0tes0oZFWQqEWvgZe9HLWaNlvXGdOrg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-locate-window@3.965.5': + resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-user-agent-browser@3.972.9': + resolution: {integrity: sha512-sn/LMzTbGjYqCCF24390WxPd6hkpoSptiUn5DzVp4cD71yqw+yGEGm1YCxyEoPXyc8qciM8UzLJcZBFslxo5Uw==} + + '@aws-sdk/util-user-agent-node@3.973.15': + resolution: {integrity: sha512-fYn3s9PtKdgQkczGZCFMgkNEe8aq1JCVbnRqjqN9RSVW43xn2RV9xdcZ3z01a48Jpkuh/xCmBKJxdLOo4Ozg7w==} + engines: {node: '>=20.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/xml-builder@3.972.17': + resolution: {integrity: sha512-Ra7hjqAZf1OXRRMueB13qex7mFJRDK/pgCvdSFemXBT8KCGnQDPoKzHY1SjN+TjJVmnpSF14W5tJ1vDamFu+Gg==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.2.4': + resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} + engines: {node: '>=18.0.0'} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.28.5': resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + '@babel/core@7.28.5': resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} engines: {node: '>=6.9.0'} + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.28.5': resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} engines: {node: '>=6.9.0'} + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.27.3': resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} @@ -482,20 +557,30 @@ packages: resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + '@babel/helper-create-class-features-plugin@7.28.5': resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-create-class-features-plugin@7.28.6': + resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-create-regexp-features-plugin@7.28.5': resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-define-polyfill-provider@0.6.5': - resolution: {integrity: sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==} + '@babel/helper-define-polyfill-provider@0.6.8': + resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -511,12 +596,22 @@ packages: resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-transforms@7.28.3': resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-optimise-call-expression@7.27.1': resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} engines: {node: '>=6.9.0'} @@ -525,6 +620,10 @@ packages: resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + '@babel/helper-remap-async-to-generator@7.27.1': resolution: {integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==} engines: {node: '>=6.9.0'} @@ -537,6 +636,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} engines: {node: '>=6.9.0'} @@ -561,11 +666,20 @@ packages: resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.28.5': resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5': resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==} engines: {node: '>=6.9.0'} @@ -590,14 +704,14 @@ packages: peerDependencies: '@babel/core': ^7.13.0 - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3': - resolution: {integrity: sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==} + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6': + resolution: {integrity: sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-proposal-decorators@7.28.0': - resolution: {integrity: sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==} + '@babel/plugin-proposal-decorators@7.29.0': + resolution: {integrity: sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -629,8 +743,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-decorators@7.27.1': - resolution: {integrity: sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==} + '@babel/plugin-syntax-decorators@7.28.6': + resolution: {integrity: sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -640,8 +754,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-assertions@7.27.1': - resolution: {integrity: sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==} + '@babel/plugin-syntax-import-assertions@7.28.6': + resolution: {integrity: sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -652,6 +766,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-attributes@7.28.6': + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-meta@7.10.4': resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: @@ -728,14 +848,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-generator-functions@7.28.0': - resolution: {integrity: sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==} + '@babel/plugin-transform-async-generator-functions@7.29.0': + resolution: {integrity: sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-to-generator@7.27.1': - resolution: {integrity: sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==} + '@babel/plugin-transform-async-to-generator@7.28.6': + resolution: {integrity: sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -746,32 +866,32 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoping@7.28.5': - resolution: {integrity: sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==} + '@babel/plugin-transform-block-scoping@7.28.6': + resolution: {integrity: sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-properties@7.27.1': - resolution: {integrity: sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==} + '@babel/plugin-transform-class-properties@7.28.6': + resolution: {integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-static-block@7.28.3': - resolution: {integrity: sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==} + '@babel/plugin-transform-class-static-block@7.28.6': + resolution: {integrity: sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.12.0 - '@babel/plugin-transform-classes@7.28.4': - resolution: {integrity: sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==} + '@babel/plugin-transform-classes@7.28.6': + resolution: {integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-computed-properties@7.27.1': - resolution: {integrity: sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==} + '@babel/plugin-transform-computed-properties@7.28.6': + resolution: {integrity: sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -782,8 +902,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-dotall-regex@7.27.1': - resolution: {integrity: sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==} + '@babel/plugin-transform-dotall-regex@7.28.6': + resolution: {integrity: sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -794,8 +914,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1': - resolution: {integrity: sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==} + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -806,14 +926,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-explicit-resource-management@7.28.0': - resolution: {integrity: sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==} + '@babel/plugin-transform-explicit-resource-management@7.28.6': + resolution: {integrity: sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-exponentiation-operator@7.28.5': - resolution: {integrity: sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==} + '@babel/plugin-transform-exponentiation-operator@7.28.6': + resolution: {integrity: sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -836,8 +956,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-json-strings@7.27.1': - resolution: {integrity: sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==} + '@babel/plugin-transform-json-strings@7.28.6': + resolution: {integrity: sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -848,8 +968,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-logical-assignment-operators@7.28.5': - resolution: {integrity: sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==} + '@babel/plugin-transform-logical-assignment-operators@7.28.6': + resolution: {integrity: sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -872,8 +992,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-systemjs@7.28.5': - resolution: {integrity: sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==} + '@babel/plugin-transform-modules-commonjs@7.28.6': + resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-systemjs@7.29.0': + resolution: {integrity: sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -884,8 +1010,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-named-capturing-groups-regex@7.27.1': - resolution: {integrity: sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==} + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -896,20 +1022,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-nullish-coalescing-operator@7.27.1': - resolution: {integrity: sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==} + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6': + resolution: {integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-numeric-separator@7.27.1': - resolution: {integrity: sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==} + '@babel/plugin-transform-numeric-separator@7.28.6': + resolution: {integrity: sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-object-rest-spread@7.28.4': - resolution: {integrity: sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==} + '@babel/plugin-transform-object-rest-spread@7.28.6': + resolution: {integrity: sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -920,8 +1046,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-catch-binding@7.27.1': - resolution: {integrity: sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==} + '@babel/plugin-transform-optional-catch-binding@7.28.6': + resolution: {integrity: sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -932,20 +1058,26 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-optional-chaining@7.28.6': + resolution: {integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-parameters@7.27.7': resolution: {integrity: sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-private-methods@7.27.1': - resolution: {integrity: sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==} + '@babel/plugin-transform-private-methods@7.28.6': + resolution: {integrity: sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-private-property-in-object@7.27.1': - resolution: {integrity: sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==} + '@babel/plugin-transform-private-property-in-object@7.28.6': + resolution: {integrity: sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -986,14 +1118,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regenerator@7.28.4': - resolution: {integrity: sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==} + '@babel/plugin-transform-regenerator@7.29.0': + resolution: {integrity: sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regexp-modifiers@7.27.1': - resolution: {integrity: sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==} + '@babel/plugin-transform-regexp-modifiers@7.28.6': + resolution: {integrity: sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1016,8 +1148,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-spread@7.27.1': - resolution: {integrity: sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==} + '@babel/plugin-transform-spread@7.28.6': + resolution: {integrity: sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1052,8 +1184,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-property-regex@7.27.1': - resolution: {integrity: sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==} + '@babel/plugin-transform-unicode-property-regex@7.28.6': + resolution: {integrity: sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1064,14 +1196,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-sets-regex@7.27.1': - resolution: {integrity: sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==} + '@babel/plugin-transform-unicode-sets-regex@7.28.6': + resolution: {integrity: sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/preset-env@7.28.5': - resolution: {integrity: sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==} + '@babel/preset-env@7.29.2': + resolution: {integrity: sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1093,10 +1225,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime-corejs3@7.28.4': - resolution: {integrity: sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==} - engines: {node: '>=6.9.0'} - '@babel/runtime@7.28.4': resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} @@ -1105,14 +1233,26 @@ packages: resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + '@babel/traverse@7.28.5': resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + '@babel/types@7.28.5': resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + '@balena/dockerignore@1.0.2': resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==} @@ -1123,85 +1263,81 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} - '@commitlint/cli@20.2.0': - resolution: {integrity: sha512-l37HkrPZ2DZy26rKiTUvdq/LZtlMcxz+PeLv9dzK9NzoFGuJdOQyYU7IEkEQj0pO++uYue89wzOpZ0hcTtoqUA==} + '@commitlint/cli@20.5.0': + resolution: {integrity: sha512-yNkyN/tuKTJS3wdVfsZ2tXDM4G4Gi7z+jW54Cki8N8tZqwKBltbIvUUrSbT4hz1bhW/h0CdR+5sCSpXD+wMKaQ==} engines: {node: '>=v18'} hasBin: true - '@commitlint/config-conventional@19.8.1': - resolution: {integrity: sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ==} + '@commitlint/config-conventional@20.5.0': + resolution: {integrity: sha512-t3Ni88rFw1XMa4nZHgOKJ8fIAT9M2j5TnKyTqJzsxea7FUetlNdYFus9dz+MhIRZmc16P0PPyEfh6X2d/qw8SA==} engines: {node: '>=v18'} - '@commitlint/config-validator@20.2.0': - resolution: {integrity: sha512-SQCBGsL9MFk8utWNSthdxd9iOD1pIVZSHxGBwYIGfd67RTjxqzFOSAYeQVXOu3IxRC3YrTOH37ThnTLjUlyF2w==} + '@commitlint/config-validator@20.5.0': + resolution: {integrity: sha512-T/Uh6iJUzyx7j35GmHWdIiGRQB+ouZDk0pwAaYq4SXgB54KZhFdJ0vYmxiW6AMYICTIWuyMxDBl1jK74oFp/Gw==} engines: {node: '>=v18'} - '@commitlint/ensure@20.2.0': - resolution: {integrity: sha512-+8TgIGv89rOWyt3eC6lcR1H7hqChAKkpawytlq9P1i/HYugFRVqgoKJ8dhd89fMnlrQTLjA5E97/4sF09QwdoA==} + '@commitlint/ensure@20.5.0': + resolution: {integrity: sha512-IpHqAUesBeW1EDDdjzJeaOxU9tnogLAyXLRBn03SHlj1SGENn2JGZqSWGkFvBJkJzfXAuCNtsoYzax+ZPS+puw==} engines: {node: '>=v18'} '@commitlint/execute-rule@20.0.0': resolution: {integrity: sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw==} engines: {node: '>=v18'} - '@commitlint/format@20.2.0': - resolution: {integrity: sha512-PhNoLNhxpfIBlW/i90uZ3yG3hwSSYx7n4d9Yc+2FAorAHS0D9btYRK4ZZXX+Gm3W5tDtu911ow/eWRfcRVgNWg==} + '@commitlint/format@20.5.0': + resolution: {integrity: sha512-TI9EwFU/qZWSK7a5qyXMpKPPv3qta7FO4tKW+Wt2al7sgMbLWTsAcDpX1cU8k16TRdsiiet9aOw0zpvRXNJu7Q==} engines: {node: '>=v18'} - '@commitlint/is-ignored@20.2.0': - resolution: {integrity: sha512-Lz0OGeZCo/QHUDLx5LmZc0EocwanneYJUM8z0bfWexArk62HKMLfLIodwXuKTO5y0s6ddXaTexrYHs7v96EOmw==} + '@commitlint/is-ignored@20.5.0': + resolution: {integrity: sha512-JWLarAsurHJhPozbuAH6GbP4p/hdOCoqS9zJMfqwswne+/GPs5V0+rrsfOkP68Y8PSLphwtFXV0EzJ+GTXTTGg==} engines: {node: '>=v18'} - '@commitlint/lint@20.2.0': - resolution: {integrity: sha512-cQEEB+jlmyQbyiji/kmh8pUJSDeUmPiWq23kFV0EtW3eM+uAaMLMuoTMajbrtWYWQpPzOMDjYltQ8jxHeHgITg==} + '@commitlint/lint@20.5.0': + resolution: {integrity: sha512-jiM3hNUdu04jFBf1VgPdjtIPvbuVfDTBAc6L98AWcoLjF5sYqkulBHBzlVWll4rMF1T5zeQFB6r//a+s+BBKlA==} engines: {node: '>=v18'} - '@commitlint/load@20.2.0': - resolution: {integrity: sha512-iAK2GaBM8sPFTSwtagI67HrLKHIUxQc2BgpgNc/UMNme6LfmtHpIxQoN1TbP+X1iz58jq32HL1GbrFTCzcMi6g==} + '@commitlint/load@20.5.0': + resolution: {integrity: sha512-sLhhYTL/KxeOTZjjabKDhwidGZan84XKK1+XFkwDYL/4883kIajcz/dZFAhBJmZPtL8+nBx6bnkzA95YxPeDPw==} engines: {node: '>=v18'} - '@commitlint/message@20.0.0': - resolution: {integrity: sha512-gLX4YmKnZqSwkmSB9OckQUrI5VyXEYiv3J5JKZRxIp8jOQsWjZgHSG/OgEfMQBK9ibdclEdAyIPYggwXoFGXjQ==} + '@commitlint/message@20.4.3': + resolution: {integrity: sha512-6akwCYrzcrFcTYz9GyUaWlhisY4lmQ3KvrnabmhoeAV8nRH4dXJAh4+EUQ3uArtxxKQkvxJS78hNX2EU3USgxQ==} engines: {node: '>=v18'} - '@commitlint/parse@20.2.0': - resolution: {integrity: sha512-LXStagGU1ivh07X7sM+hnEr4BvzFYn1iBJ6DRg2QsIN8lBfSzyvkUcVCDwok9Ia4PWiEgei5HQjju6xfJ1YaSQ==} + '@commitlint/parse@20.5.0': + resolution: {integrity: sha512-SeKWHBMk7YOTnnEWUhx+d1a9vHsjjuo6Uo1xRfPNfeY4bdYFasCH1dDpAv13Lyn+dDPOels+jP6D2GRZqzc5fA==} engines: {node: '>=v18'} - '@commitlint/read@20.2.0': - resolution: {integrity: sha512-+SjF9mxm5JCbe+8grOpXCXMMRzAnE0WWijhhtasdrpJoAFJYd5UgRTj/oCq5W3HJTwbvTOsijEJ0SUGImECD7Q==} + '@commitlint/read@20.5.0': + resolution: {integrity: sha512-JDEIJ2+GnWpK8QqwfmW7O42h0aycJEWNqcdkJnyzLD11nf9dW2dWLTVEa8Wtlo4IZFGLPATjR5neA5QlOvIH1w==} engines: {node: '>=v18'} - '@commitlint/resolve-extends@20.2.0': - resolution: {integrity: sha512-KVoLDi9BEuqeq+G0wRABn4azLRiCC22/YHR2aCquwx6bzCHAIN8hMt3Nuf1VFxq/c8ai6s8qBxE8+ZD4HeFTlQ==} + '@commitlint/resolve-extends@20.5.0': + resolution: {integrity: sha512-3SHPWUW2v0tyspCTcfSsYml0gses92l6TlogwzvM2cbxDgmhSRc+fldDjvGkCXJrjSM87BBaWYTPWwwyASZRrg==} engines: {node: '>=v18'} - '@commitlint/rules@20.2.0': - resolution: {integrity: sha512-27rHGpeAjnYl/A+qUUiYDa7Yn1WIjof/dFJjYW4gA1Ug+LUGa1P0AexzGZ5NBxTbAlmDgaxSZkLLxtLVqtg8PQ==} + '@commitlint/rules@20.5.0': + resolution: {integrity: sha512-5NdQXQEdnDPT5pK8O39ZA7HohzPRHEsDGU23cyVCNPQy4WegAbAwrQk3nIu7p2sl3dutPk8RZd91yKTrMTnRkQ==} engines: {node: '>=v18'} '@commitlint/to-lines@20.0.0': resolution: {integrity: sha512-2l9gmwiCRqZNWgV+pX1X7z4yP0b3ex/86UmUFgoRt672Ez6cAM2lOQeHFRUTuE6sPpi8XBCGnd8Kh3bMoyHwJw==} engines: {node: '>=v18'} - '@commitlint/top-level@20.0.0': - resolution: {integrity: sha512-drXaPSP2EcopukrUXvUXmsQMu3Ey/FuJDc/5oiW4heoCfoE5BdLQyuc7veGeE3aoQaTVqZnh4D5WTWe2vefYKg==} + '@commitlint/top-level@20.4.3': + resolution: {integrity: sha512-qD9xfP6dFg5jQ3NMrOhG0/w5y3bBUsVGyJvXxdWEwBm8hyx4WOk3kKXw28T5czBYvyeCVJgJJ6aoJZUWDpaacQ==} engines: {node: '>=v18'} - '@commitlint/types@19.8.1': - resolution: {integrity: sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==} + '@commitlint/types@20.5.0': + resolution: {integrity: sha512-ZJoS8oSq2CAZEpc/YI9SulLrdiIyXeHb/OGqGrkUP6Q7YV+0ouNAa7GjqRdXeQPncHQIDz/jbCTlHScvYvO/gA==} engines: {node: '>=v18'} - '@commitlint/types@20.2.0': - resolution: {integrity: sha512-KTy0OqRDLR5y/zZMnizyx09z/rPlPC/zKhYgH8o/q6PuAjoQAKlRfY4zzv0M64yybQ//6//4H1n14pxaLZfUnA==} - engines: {node: '>=v18'} - - '@conventional-changelog/git-client@2.5.1': - resolution: {integrity: sha512-lAw7iA5oTPWOLjiweb7DlGEMDEvzqzLLa6aWOly2FSZ64IwLE8T458rC+o+WvI31Doz6joM7X2DoNog7mX8r4A==} + '@conventional-changelog/git-client@2.6.0': + resolution: {integrity: sha512-T+uPDciKf0/ioNNDpMGc8FDsehJClZP0yR3Q5MN6wE/Y/1QZ7F+80OgznnTCOlMEG4AV0LvH2UJi3C/nBnaBUg==} engines: {node: '>=18'} peerDependencies: conventional-commits-filter: ^5.0.0 - conventional-commits-parser: ^6.1.0 + conventional-commits-parser: ^6.3.0 peerDependenciesMeta: conventional-commits-filter: optional: true @@ -1550,12 +1686,12 @@ packages: search-insights: optional: true - '@docusaurus/babel@3.9.2': - resolution: {integrity: sha512-GEANdi/SgER+L7Japs25YiGil/AUDnFFHaCGPBbundxoWtCkA2lmy7/tFmgED4y1htAy6Oi4wkJEQdGssnw9MA==} + '@docusaurus/babel@3.10.0': + resolution: {integrity: sha512-mqCJhCZNZUDg0zgDEaPTM4DnRsisa24HdqTy/qn/MQlbwhTb4WVaZg6ZyX6yIVKqTz8fS1hBMgM+98z+BeJJDg==} engines: {node: '>=20.0'} - '@docusaurus/bundler@3.9.2': - resolution: {integrity: sha512-ZOVi6GYgTcsZcUzjblpzk3wH1Fya2VNpd5jtHoCCFcJlMQ1EYXZetfAnRHLcyiFeBABaI1ltTYbOBtH/gahGVA==} + '@docusaurus/bundler@3.10.0': + resolution: {integrity: sha512-iONUGZGgp+lAkw/cJZH6irONcF4p8+278IsdRlq8lYhxGjkoNUs0w7F4gVXBYSNChq5KG5/JleTSsdJySShxow==} engines: {node: '>=20.0'} peerDependencies: '@docusaurus/faster': '*' @@ -1563,106 +1699,116 @@ packages: '@docusaurus/faster': optional: true - '@docusaurus/core@3.9.2': - resolution: {integrity: sha512-HbjwKeC+pHUFBfLMNzuSjqFE/58+rLVKmOU3lxQrpsxLBOGosYco/Q0GduBb0/jEMRiyEqjNT/01rRdOMWq5pw==} + '@docusaurus/core@3.10.0': + resolution: {integrity: sha512-mgLdQsO8xppnQZc3LPi+Mf+PkPeyxJeIx11AXAq/14fsaMefInQiMEZUUmrc7J+956G/f7MwE7tn8KZgi3iRcA==} engines: {node: '>=20.0'} hasBin: true peerDependencies: + '@docusaurus/faster': '*' '@mdx-js/react': ^3.0.0 react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@docusaurus/faster': + optional: true + + '@docusaurus/cssnano-preset@3.10.0': + resolution: {integrity: sha512-qzSshTO1DB3TYW+dPUal5KHM7XPc5YQfzF3Kdb2NDACJUyGbNcFtw3tGkCJlYwhNCRKbZcmwraKUS1i5dcHdGg==} + engines: {node: '>=20.0'} - '@docusaurus/cssnano-preset@3.9.2': - resolution: {integrity: sha512-8gBKup94aGttRduABsj7bpPFTX7kbwu+xh3K9NMCF5K4bWBqTFYW+REKHF6iBVDHRJ4grZdIPbvkiHd/XNKRMQ==} + '@docusaurus/faster@3.10.0': + resolution: {integrity: sha512-GNPtVH14ISjHfSwnHu3KiFGf86ICmJSQDeSv/QaanpBgiZGOtgZaslnC5q8WiguxM1EVkwcGxPuD8BXF4eggKw==} engines: {node: '>=20.0'} + peerDependencies: + '@docusaurus/types': '*' - '@docusaurus/logger@3.9.2': - resolution: {integrity: sha512-/SVCc57ByARzGSU60c50rMyQlBuMIJCjcsJlkphxY6B0GV4UH3tcA1994N8fFfbJ9kX3jIBe/xg3XP5qBtGDbA==} + '@docusaurus/logger@3.10.0': + resolution: {integrity: sha512-9jrZzFuBH1LDRlZ7cznAhCLmAZ3HSDqgwdrSSZdGHq9SPUOQgXXu8mnxe2ZRB9NS1PCpMTIOVUqDtZPIhMafZg==} engines: {node: '>=20.0'} - '@docusaurus/mdx-loader@3.9.2': - resolution: {integrity: sha512-wiYoGwF9gdd6rev62xDU8AAM8JuLI/hlwOtCzMmYcspEkzecKrP8J8X+KpYnTlACBUUtXNJpSoCwFWJhLRevzQ==} + '@docusaurus/mdx-loader@3.10.0': + resolution: {integrity: sha512-mQQV97080AH4PYNs087l202NMDqRopZA4mg5W76ZZyTFrmWhJ3mHg+8A+drJVENxw5/Q+wHMHLgsx+9z1nEs0A==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/module-type-aliases@3.9.2': - resolution: {integrity: sha512-8qVe2QA9hVLzvnxP46ysuofJUIc/yYQ82tvA/rBTrnpXtCjNSFLxEZfd5U8cYZuJIVlkPxamsIgwd5tGZXfvew==} + '@docusaurus/module-type-aliases@3.10.0': + resolution: {integrity: sha512-/1O0Zg8w3DFrYX/I6Fbss7OJrtZw1QoyjDhegiFNHVi9A9Y0gQ3jUAytVxF6ywpAWpLyLxch8nN8H/V3XfzdJQ==} peerDependencies: react: '*' react-dom: '*' - '@docusaurus/plugin-content-blog@3.9.2': - resolution: {integrity: sha512-3I2HXy3L1QcjLJLGAoTvoBnpOwa6DPUa3Q0dMK19UTY9mhPkKQg/DYhAGTiBUKcTR0f08iw7kLPqOhIgdV3eVQ==} + '@docusaurus/plugin-content-blog@3.10.0': + resolution: {integrity: sha512-RuTz68DhB7CL96QO5UsFbciD7GPYq6QV+YMfF9V0+N4ZgLhJIBgpVAr8GobrKF6NRe5cyWWETU5z5T834piG9g==} engines: {node: '>=20.0'} peerDependencies: '@docusaurus/plugin-content-docs': '*' react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-content-docs@3.9.2': - resolution: {integrity: sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==} + '@docusaurus/plugin-content-docs@3.10.0': + resolution: {integrity: sha512-9BjHhf15ct8Z7TThTC0xRndKDVvMKmVsAGAN7W9FpNRzfMdScOGcXtLmcCWtJGvAezjOJIm6CxOYCy3Io5+RnQ==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-content-pages@3.9.2': - resolution: {integrity: sha512-s4849w/p4noXUrGpPUF0BPqIAfdAe76BLaRGAGKZ1gTDNiGxGcpsLcwJ9OTi1/V8A+AzvsmI9pkjie2zjIQZKA==} + '@docusaurus/plugin-content-pages@3.10.0': + resolution: {integrity: sha512-5amX8kEJI+nIGtuLVjYk59Y5utEJ3CHETFOPEE4cooIRLA4xM4iBsA6zFgu4ljcopeYwvBzFEWf5g2I6Yb9SkA==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-css-cascade-layers@3.9.2': - resolution: {integrity: sha512-w1s3+Ss+eOQbscGM4cfIFBlVg/QKxyYgj26k5AnakuHkKxH6004ZtuLe5awMBotIYF2bbGDoDhpgQ4r/kcj4rQ==} + '@docusaurus/plugin-css-cascade-layers@3.10.0': + resolution: {integrity: sha512-6q1vtt5FJcg5osgkHeM1euErECNqEZ5Z1j69yiNx2luEBIso+nxCkS9nqj8w+MK5X7rvKEToGhFfOFWncs51pQ==} engines: {node: '>=20.0'} - '@docusaurus/plugin-debug@3.9.2': - resolution: {integrity: sha512-j7a5hWuAFxyQAkilZwhsQ/b3T7FfHZ+0dub6j/GxKNFJp2h9qk/P1Bp7vrGASnvA9KNQBBL1ZXTe7jlh4VdPdA==} + '@docusaurus/plugin-debug@3.10.0': + resolution: {integrity: sha512-XcljKN+G+nmmK69uQA1d9BlYU3ZftG3T3zpK8/7Hf/wrOlV7TA4Ampdrdwkg0jElKdKAoSnPhCO0/U3bQGsVQQ==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-google-analytics@3.9.2': - resolution: {integrity: sha512-mAwwQJ1Us9jL/lVjXtErXto4p4/iaLlweC54yDUK1a97WfkC6Z2k5/769JsFgwOwOP+n5mUQGACXOEQ0XDuVUw==} + '@docusaurus/plugin-google-analytics@3.10.0': + resolution: {integrity: sha512-hTEoodatpBZnUat5nFExbuTGA1lhWGy7vZGuTew5Q3QDtGKFpSJLYmZJhdTjvCFwv1+qQ67hgAVlKdJOB8TXow==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-google-gtag@3.9.2': - resolution: {integrity: sha512-YJ4lDCphabBtw19ooSlc1MnxtYGpjFV9rEdzjLsUnBCeis2djUyCozZaFhCg6NGEwOn7HDDyMh0yzcdRpnuIvA==} + '@docusaurus/plugin-google-gtag@3.10.0': + resolution: {integrity: sha512-iB/Zzjv/eelJRbdULZqzWCbgMgJ7ht4ONVjXtN3+BI/muil6S87gQ1OJyPwlXD+ELdKkitC7bWv5eJdYOZLhrQ==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-google-tag-manager@3.9.2': - resolution: {integrity: sha512-LJtIrkZN/tuHD8NqDAW1Tnw0ekOwRTfobWPsdO15YxcicBo2ykKF0/D6n0vVBfd3srwr9Z6rzrIWYrMzBGrvNw==} + '@docusaurus/plugin-google-tag-manager@3.10.0': + resolution: {integrity: sha512-FEjZxqKgLHa+Wez/EgKxRwvArNCWIScfyEQD95rot7jkxp6nonjI5XIbGfO/iYhM5Qinwe8aIEQHP2KZtpqVuA==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-sitemap@3.9.2': - resolution: {integrity: sha512-WLh7ymgDXjG8oPoM/T4/zUP7KcSuFYRZAUTl8vR6VzYkfc18GBM4xLhcT+AKOwun6kBivYKUJf+vlqYJkm+RHw==} + '@docusaurus/plugin-sitemap@3.10.0': + resolution: {integrity: sha512-DVTSLjB97hIjmayGnGcBfognCeI7ZuUKgEnU7Oz81JYqXtVg94mVTthDjq3QHTylYNeCUbkaW8VF0FDLcc8pPw==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-svgr@3.9.2': - resolution: {integrity: sha512-n+1DE+5b3Lnf27TgVU5jM1d4x5tUh2oW5LTsBxJX4PsAPV0JGcmI6p3yLYtEY0LRVEIJh+8RsdQmRE66wSV8mw==} + '@docusaurus/plugin-svgr@3.10.0': + resolution: {integrity: sha512-lNljBESaETZqVBMPqkrGchr+UPT1eZzEPLmJhz8I76BxbjqgsUnRvrq6lQJ9sYjgmgX52KB7kkgczqd2yzoswQ==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/preset-classic@3.9.2': - resolution: {integrity: sha512-IgyYO2Gvaigi21LuDIe+nvmN/dfGXAiMcV/murFqcpjnZc7jxFAxW+9LEjdPt61uZLxG4ByW/oUmX/DDK9t/8w==} + '@docusaurus/preset-classic@3.10.0': + resolution: {integrity: sha512-kw/Ye02Hc6xP1OdTswy8yxQEHg0fdPpyWAQRxr5b2x3h7LlG2Zgbb5BDFROnXDDMpUxB7YejlocJIE5HIEfpNA==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 @@ -1673,53 +1819,73 @@ packages: peerDependencies: react: '*' - '@docusaurus/theme-classic@3.9.2': - resolution: {integrity: sha512-IGUsArG5hhekXd7RDb11v94ycpJpFdJPkLnt10fFQWOVxAtq5/D7hT6lzc2fhyQKaaCE62qVajOMKL7OiAFAIA==} + '@docusaurus/theme-classic@3.10.0': + resolution: {integrity: sha512-9msCAsRdN+UG+RwPwCFb0uKy4tGoPh5YfBozXeGUtIeAgsMdn6f3G/oY861luZ3t8S2ET8S9Y/1GnpJAGWytww==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/theme-common@3.9.2': - resolution: {integrity: sha512-6c4DAbR6n6nPbnZhY2V3tzpnKnGL+6aOsLvFL26VRqhlczli9eWG0VDUNoCQEPnGwDMhPS42UhSAnz5pThm5Ag==} + '@docusaurus/theme-common@3.10.0': + resolution: {integrity: sha512-Dkp1YXKn16ByCJAdIjbDIOpVb4Z66MsVD694/ilX1vAAHaVEMrVsf/NPd9VgreyFx08rJ9GqV1MtzsbTcU73Kg==} engines: {node: '>=20.0'} peerDependencies: '@docusaurus/plugin-content-docs': '*' react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/theme-search-algolia@3.9.2': - resolution: {integrity: sha512-GBDSFNwjnh5/LdkxCKQHkgO2pIMX1447BxYUBG2wBiajS21uj64a+gH/qlbQjDLxmGrbrllBrtJkUHxIsiwRnw==} + '@docusaurus/theme-search-algolia@3.10.0': + resolution: {integrity: sha512-f5FPKI08e3JRG63vR/o4qeuUVHUHzFzM0nnF+AkB67soAZgNsKJRf2qmUZvlQkGwlV+QFkKe4D0ANMh1jToU3g==} engines: {node: '>=20.0'} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/theme-translations@3.9.2': - resolution: {integrity: sha512-vIryvpP18ON9T9rjgMRFLr2xJVDpw1rtagEGf8Ccce4CkTrvM/fRB8N2nyWYOW5u3DdjkwKw5fBa+3tbn9P4PA==} + '@docusaurus/theme-translations@3.10.0': + resolution: {integrity: sha512-L9IbFLwTc5+XdgH45iQYufLn0SVZd6BUNelDbKIFlH+E4hhjuj/XHWAFMX/w2K59rfy8wak9McOaei7BSUfRPA==} engines: {node: '>=20.0'} - '@docusaurus/tsconfig@3.9.2': - resolution: {integrity: sha512-j6/Fp4Rlpxsc632cnRnl5HpOWeb6ZKssDj6/XzzAzVGXXfm9Eptx3rxCC+fDzySn9fHTS+CWJjPineCR1bB5WQ==} + '@docusaurus/tsconfig@3.10.0': + resolution: {integrity: sha512-TXdC3WXuPrdQAexLvjUJfnYf3YKEgEqAs5nK0Q88pRBCW7t7oN4ILvWYb3A5Z1wlSXyXGWW/mCUmLEhdWsjnDQ==} - '@docusaurus/types@3.9.2': - resolution: {integrity: sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==} + '@docusaurus/types@3.10.0': + resolution: {integrity: sha512-F0dOt3FOoO20rRaFK7whGFQZ3ggyrWEdQc/c8/UiRuzhtg4y1w9FspXH5zpCT07uMnJKBPGh+qNazbNlCQqvSw==} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/utils-common@3.9.2': - resolution: {integrity: sha512-I53UC1QctruA6SWLvbjbhCpAw7+X7PePoe5pYcwTOEXD/PxeP8LnECAhTHHwWCblyUX5bMi4QLRkxvyZ+IT8Aw==} + '@docusaurus/utils-common@3.10.0': + resolution: {integrity: sha512-JyL7sb9QVDgYvudIS81Dv0lsWm7le0vGZSDwsztxWam1SPBqrnkvBy9UYL/amh6pbybkyYTd3CMTkO24oMlCSw==} engines: {node: '>=20.0'} - '@docusaurus/utils-validation@3.9.2': - resolution: {integrity: sha512-l7yk3X5VnNmATbwijJkexdhulNsQaNDwoagiwujXoxFbWLcxHQqNQ+c/IAlzrfMMOfa/8xSBZ7KEKDesE/2J7A==} + '@docusaurus/utils-validation@3.10.0': + resolution: {integrity: sha512-c+6n2+ZPOJtWWc8Bb/EYdpSDfjYEScdCu9fB/SNjOmSCf1IdVnGf2T53o0tsz0gDRtCL90tifTL0JE/oMuP1Mw==} engines: {node: '>=20.0'} - '@docusaurus/utils@3.9.2': - resolution: {integrity: sha512-lBSBiRruFurFKXr5Hbsl2thmGweAPmddhF3jb99U4EMDA5L+e5Y1rAkOS07Nvrup7HUMBDrCV45meaxZnt28nQ==} + '@docusaurus/utils@3.10.0': + resolution: {integrity: sha512-T3B0WTigsIthe0D4LQa2k+7bJY+c3WS+Wq2JhcznOSpn1lSN64yNtHQXboCj3QnUs1EuAZszQG1SHKu5w5ZrlA==} engines: {node: '>=20.0'} + '@edge-runtime/format@2.2.1': + resolution: {integrity: sha512-JQTRVuiusQLNNLe2W9tnzBlV/GvSVcozLl4XZHk5swnRZ/v6jp8TqR8P7sqmJsQqblDZ3EztcWmLDbhRje/+8g==} + engines: {node: '>=16'} + + '@edge-runtime/node-utils@2.3.0': + resolution: {integrity: sha512-uUtx8BFoO1hNxtHjp3eqVPC/mWImGb2exOfGjMLUoipuWgjej+f4o/VP4bUI8U40gu7Teogd5VTeZUkGvJSPOQ==} + engines: {node: '>=16'} + + '@edge-runtime/ponyfill@2.4.2': + resolution: {integrity: sha512-oN17GjFr69chu6sDLvXxdhg0Qe8EZviGSuqzR9qOiKh4MhFYGdBBcqRNzdmYeAdeRzOW2mM9yil4RftUQ7sUOA==} + engines: {node: '>=16'} + + '@edge-runtime/primitives@4.1.0': + resolution: {integrity: sha512-Vw0lbJ2lvRUqc7/soqygUX216Xb8T3WBZ987oywz6aJqRxcwSVWwr9e+Nqo2m9bxobA9mdbWNNoRY6S9eko1EQ==} + engines: {node: '>=16'} + + '@edge-runtime/vm@3.2.0': + resolution: {integrity: sha512-0dEVyRLM/lG4gp1R/Ik5bfPl/1wX00xFwd5KcNH602tzBa09oF7pbTKETEhR1GjZ75K6OJnYFu8II2dyMhONMw==} + engines: {node: '>=16'} + '@emnapi/core@1.7.1': resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} @@ -1752,171 +1918,333 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.27.1': resolution: {integrity: sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.27.1': resolution: {integrity: sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.27.1': resolution: {integrity: sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.27.1': resolution: {integrity: sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.27.1': resolution: {integrity: sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.27.1': resolution: {integrity: sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.27.1': resolution: {integrity: sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.27.1': + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.1': resolution: {integrity: sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.27.1': resolution: {integrity: sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.27.1': resolution: {integrity: sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.27.1': resolution: {integrity: sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.27.1': resolution: {integrity: sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.27.1': resolution: {integrity: sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.27.1': resolution: {integrity: sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.27.1': resolution: {integrity: sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.27.1': resolution: {integrity: sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.27.1': resolution: {integrity: sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.27.1': resolution: {integrity: sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.27.1': resolution: {integrity: sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.27.1': resolution: {integrity: sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.27.1': resolution: {integrity: sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.27.1': resolution: {integrity: sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.27.1': resolution: {integrity: sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.27.1': resolution: {integrity: sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.27.1': resolution: {integrity: sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.0': resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/regexpp@4.12.2': resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/compat@2.0.0': - resolution: {integrity: sha512-T9AfE1G1uv4wwq94ozgTGio5EUQBqAVe1X9qsQtSNVEYW6j3hvtZVm8Smr4qL1qDPFg+lOB2cL5RxTRMzq4CTA==} + '@eslint/compat@2.0.4': + resolution: {integrity: sha512-o598tCGstJv9Kk4XapwP+oDij9HD9Qr3V37ABzTfdzVvbFciV+sfg9zSW6olj6G/IXj7p89SwSzPnZ+JUEPIPg==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} peerDependencies: - eslint: ^8.40 || 9 + eslint: ^8.40 || 9 || 10 peerDependenciesMeta: eslint: optional: true @@ -1933,14 +2261,18 @@ packages: resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@1.0.0': - resolution: {integrity: sha512-PRfWP+8FOldvbApr6xL7mNCw4cJcSTq4GA7tYbgq15mRb0kWKO/wEB2jr+uwjFH3sZvEZneZyCUGTxsv4Sahyw==} + '@eslint/core@1.2.0': + resolution: {integrity: sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/eslintrc@3.3.3': resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@9.39.2': resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1956,53 +2288,45 @@ packages: '@exodus/schemasafe@1.3.0': resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} - '@faker-js/faker@10.2.0': - resolution: {integrity: sha512-rTXwAsIxpCqzUnZvrxVh3L0QA0NzToqWBLAhV+zDV3MIIwiQhAZHMdPCIaj5n/yADu/tyk12wIPgL6YHGXJP+g==} + '@faker-js/faker@10.4.0': + resolution: {integrity: sha512-sDBWI3yLy8EcDzgobvJTWq1MJYzAkQdpjXuPukga9wXonhpMRvd1Izuo2Qgwey2OiEoRIBr35RMU9HJRoOHzpw==} engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'} '@faker-js/faker@5.5.3': resolution: {integrity: sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw==} deprecated: Please update to a newer version. - '@formatjs/ecma402-abstract@2.3.6': - resolution: {integrity: sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==} + '@fastify/busboy@2.1.1': + resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} + engines: {node: '>=14'} + + '@formatjs/bigdecimal@0.2.0': + resolution: {integrity: sha512-GeaxHZbUoYvHL9tC5eltHLs+1zU70aPw0s7LwqgktIzF5oMhNY4o4deEtusJMsq7WFJF3Ye2zQEzdG8beVk73w==} + + '@formatjs/ecma402-abstract@3.2.0': + resolution: {integrity: sha512-dHnqHgBo6GXYGRsepaE1wmsC2etaivOWd5VaJstZd+HI2zR3DCUjbDVZRtoPGkkXZmyHvBwrdEUuqfvzhF/DtQ==} - '@formatjs/fast-memoize@2.2.7': - resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==} + '@formatjs/fast-memoize@3.1.1': + resolution: {integrity: sha512-CbNbf+tlJn1baRnPkNePnBqTLxGliG6DDgNa/UtV66abwIjwsliPMOt0172tzxABYzSuxZBZfcp//qI8AvBWPg==} - '@formatjs/icu-messageformat-parser@2.11.4': - resolution: {integrity: sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==} + '@formatjs/icu-messageformat-parser@3.5.3': + resolution: {integrity: sha512-HJWZ9S6JWey6iY5+YXE3Kd0ofWU1sC2KTTp56e1168g/xxWvVvr8k9G4fexIgwYV9wbtjY7kGYK5FjoWB3B2OQ==} - '@formatjs/icu-skeleton-parser@1.8.16': - resolution: {integrity: sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==} + '@formatjs/icu-skeleton-parser@2.1.3': + resolution: {integrity: sha512-9mFp8TJ166ZM2pcjKwsBWXrDnOJGT7vMEScVgLygUODPOsE8S6f/FHoacvrlHK1B4dYZk8vSCNruyPU64AfgJQ==} - '@formatjs/intl-localematcher@0.6.2': - resolution: {integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==} + '@formatjs/intl-localematcher@0.8.2': + resolution: {integrity: sha512-q05KMYGJLyqFNFtIb8NhWLF5X3aK/k0wYt7dnRFuy6aLQL+vUwQ1cg5cO4qawEiINybeCPXAWlprY2mSBjSXAQ==} - '@formatjs/ts-transformer@3.14.2': - resolution: {integrity: sha512-c47ij+2Xi4jMDO3Hz01BDF3yB4575Gkoq24sFzVw1K1kpHvITsFfdlXQbhxScBwJi2gBhMpuZ++XsTUZ9O0Law==} + '@formatjs/ts-transformer@4.4.3': + resolution: {integrity: sha512-K7hAYg8RRrgdvygvgwBYPmXCZwiXW0Q9XNorxg9z9ahiPKMXpumvmAV4PLu8/9qIqCRL7wANLGhYl9PyB/CqPA==} + engines: {node: '>= 20.12.0'} peerDependencies: ts-jest: ^29 peerDependenciesMeta: ts-jest: optional: true - '@google-cloud/paginator@5.0.2': - resolution: {integrity: sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==} - engines: {node: '>=14.0.0'} - - '@google-cloud/projectify@4.0.0': - resolution: {integrity: sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==} - engines: {node: '>=14.0.0'} - - '@google-cloud/promisify@4.0.0': - resolution: {integrity: sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==} - engines: {node: '>=14'} - - '@google-cloud/storage@7.18.0': - resolution: {integrity: sha512-r3ZwDMiz4nwW6R922Z1pwpePxyRwE5GdevYX63hRmAQUkUQJcBH/79EnQPDv5cOv1mFBgevdNWQfi3tie3dHrQ==} - engines: {node: '>=14'} - '@grpc/grpc-js@1.14.3': resolution: {integrity: sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==} engines: {node: '>=12.10.0'} @@ -2026,6 +2350,12 @@ packages: '@hapi/topo@5.1.0': resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + '@hono/node-server@1.19.13': + resolution: {integrity: sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@hookform/error-message@2.0.1': resolution: {integrity: sha512-U410sAr92xgxT1idlu9WWOVjndxLdgPUHEB8Schr27C9eh7/xUnITWpCMF93s+lGiG++D4JnbSnrb5A21AdSNg==} peerDependencies: @@ -2033,6 +2363,10 @@ packages: react-dom: '>=16.8.0' react-hook-form: ^7.0.0 + '@httptoolkit/esm@3.3.2': + resolution: {integrity: sha512-mpB6FdMtn+c17RA6b6PDdsJXBLHG4P8ZJFK7sjS4IZn689kcOwjt90Ct+oxTaYo9pvCSb/GeTSjYEzoBv+bnbQ==} + engines: {node: '>=6'} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -2049,53 +2383,53 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@inquirer/ansi@1.0.2': - resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} - engines: {node: '>=18'} + '@inquirer/ansi@2.0.5': + resolution: {integrity: sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - '@inquirer/core@10.3.2': - resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} - engines: {node: '>=18'} + '@inquirer/core@11.1.8': + resolution: {integrity: sha512-/u+yJk2pOKNDOh1ZgdUH2RQaRx6OOH4I0uwL95qPvTFTIL38YBsuSC4r1yXBB3Q6JvNqFFc202gk0Ew79rrcjA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/expand@4.0.23': - resolution: {integrity: sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==} - engines: {node: '>=18'} + '@inquirer/expand@5.0.11': + resolution: {integrity: sha512-yxSO89MQ7t4LTCwtsXQ/ppcfw2otLsum6nF+TM9pKesy3k2AhVDUIkaiJIwG6lzm/csc5n38MaFKLY0TrSHzEA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/figures@1.0.15': - resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} - engines: {node: '>=18'} + '@inquirer/figures@2.0.5': + resolution: {integrity: sha512-NsSs4kzfm12lNetHwAn3GEuH317IzpwrMCbOuMIVytpjnJ90YYHNwdRgYGuKmVxwuIqSgqk3M5qqQt1cDk0tGQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - '@inquirer/input@4.3.1': - resolution: {integrity: sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==} - engines: {node: '>=18'} + '@inquirer/input@5.0.11': + resolution: {integrity: sha512-twUWidn4ocPO8qi6fRM7tNWt7W1FOnOZqQ+/+PsfLUacMR5rFLDPK9ql0nBPwxi0oELbo8T5NhRs8B2+qQEqFQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/select@4.4.2': - resolution: {integrity: sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==} - engines: {node: '>=18'} + '@inquirer/select@5.1.3': + resolution: {integrity: sha512-zYyqWgGQi3NhBcNq4Isc5rB3oEdQEh1Q/EcAnOW0FK4MpnXWkvSBYgA4cYrTM4A9UB573omouZbnL9JJ74Mq3A==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/type@3.0.10': - resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} - engines: {node: '>=18'} + '@inquirer/type@4.0.5': + resolution: {integrity: sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -2114,6 +2448,10 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/cliui@9.0.0': + resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} + engines: {node: '>=18'} + '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -2126,12 +2464,12 @@ packages: resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} - '@jest/console@30.2.0': - resolution: {integrity: sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==} + '@jest/console@30.3.0': + resolution: {integrity: sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/core@30.2.0': - resolution: {integrity: sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==} + '@jest/core@30.3.0': + resolution: {integrity: sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 @@ -2143,6 +2481,10 @@ packages: resolution: {integrity: sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/diff-sequences@30.3.0': + resolution: {integrity: sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/environment-jsdom-abstract@30.2.0': resolution: {integrity: sha512-kazxw2L9IPuZpQ0mEt9lu9Z98SqR74xcagANmMBU16X0lS23yPc0+S6hGLUz8kVRlomZEs/5S/Zlpqwf5yu6OQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -2157,32 +2499,44 @@ packages: resolution: {integrity: sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/environment@30.3.0': + resolution: {integrity: sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/expect-utils@30.2.0': resolution: {integrity: sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/expect@30.2.0': - resolution: {integrity: sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==} + '@jest/expect-utils@30.3.0': + resolution: {integrity: sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect@30.3.0': + resolution: {integrity: sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} '@jest/fake-timers@30.2.0': resolution: {integrity: sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/fake-timers@30.3.0': + resolution: {integrity: sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/get-type@30.1.0': resolution: {integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/globals@30.2.0': - resolution: {integrity: sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==} + '@jest/globals@30.3.0': + resolution: {integrity: sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} '@jest/pattern@30.0.1': resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/reporters@30.2.0': - resolution: {integrity: sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==} + '@jest/reporters@30.3.0': + resolution: {integrity: sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 @@ -2198,24 +2552,24 @@ packages: resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/snapshot-utils@30.2.0': - resolution: {integrity: sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==} + '@jest/snapshot-utils@30.3.0': + resolution: {integrity: sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} '@jest/source-map@30.0.1': resolution: {integrity: sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/test-result@30.2.0': - resolution: {integrity: sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==} + '@jest/test-result@30.3.0': + resolution: {integrity: sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/test-sequencer@30.2.0': - resolution: {integrity: sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==} + '@jest/test-sequencer@30.3.0': + resolution: {integrity: sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/transform@30.2.0': - resolution: {integrity: sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==} + '@jest/transform@30.3.0': + resolution: {integrity: sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} '@jest/types@29.6.3': @@ -2226,6 +2580,10 @@ packages: resolution: {integrity: sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/types@30.3.0': + resolution: {integrity: sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -2251,9 +2609,6 @@ packages: '@js-sdsl/ordered-map@4.4.2': resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} - '@jsdevtools/ono@7.1.3': - resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} - '@jsep-plugin/assignment@1.3.0': resolution: {integrity: sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==} engines: {node: '>= 10.16.0'} @@ -2331,16 +2686,22 @@ packages: peerDependencies: koa: ^2.0.0 || ^3.0.0 + '@kwsites/file-exists@1.1.1': + resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} + + '@kwsites/promise-deferred@1.1.1': + resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + '@leichtgewicht/ip-codec@2.0.5': resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} - '@lerna-lite/changed@4.10.2': - resolution: {integrity: sha512-9gua2+7owjyvHPWDOJYumNNGhHxMCbCkYM/rv/cemafvj7ncHcCvVydPTVBuSNwayC/ufSk+o0zFCtF91JixSQ==} - engines: {node: ^20.17.0 || >=22.9.0} + '@lerna-lite/changed@5.0.0': + resolution: {integrity: sha512-BnhK576DhYQo4B9r0Jy5PAEG3tWCQgeSDRaRqa44tfmSsDaGE4cYTHjSf3gN+ponl5xSL41bIH059uR+2PjQdQ==} + engines: {node: ^22.17.0 || >=24.0.0} - '@lerna-lite/cli@4.10.2': - resolution: {integrity: sha512-yT5/z/FvVKWO9NDF7VTN5bYkuF96yH5VPl1I6n37BOOj/KFtPSG2SEMclbkJ+CCYw/kmV7sxHeodIyWnpJ8Ggw==} - engines: {node: ^20.17.0 || >=22.9.0} + '@lerna-lite/cli@5.0.0': + resolution: {integrity: sha512-kb9MXI+t6EMdPTreUEAGXQPAtnqGhiXDll1lSWFNFwZ+XWI1mj/tB2UDDbE7/g41r4p17yI1J8RYUyFIzFMaFA==} + engines: {node: ^22.17.0 || >=24.0.0} hasBin: true peerDependencies: '@lerna-lite/exec': '*' @@ -2363,29 +2724,34 @@ packages: '@lerna-lite/watch': optional: true - '@lerna-lite/core@4.10.2': - resolution: {integrity: sha512-LTGO6tWIBHi5clHECz5tqhtWCUedlyX4n93SbIP1n4ehPS+qSuHh0fJ3QvQQX18jvktKFwaR7AKzrL21lxtGYQ==} - engines: {node: ^20.17.0 || >=22.9.0} + '@lerna-lite/core@5.0.0': + resolution: {integrity: sha512-A30bmrqhKsUVRyfQeCeLo3ht9s+l2Rdi++MxBKlhK/Zwnkq3sv9l2iIiU91ItKJ3FP2FO7EEzOWxrp0Wcwgwdg==} + engines: {node: ^22.17.0 || >=24.0.0} - '@lerna-lite/init@4.10.2': - resolution: {integrity: sha512-TNnSQ7ewaY/jSvJGQSTRQT+vHt/wa3LKEEM7jkH4VSG4wCbzIV2u3xCOZ1n+oVaBaH2FO54qUKaqU8qruxVy4g==} - engines: {node: ^20.17.0 || >=22.9.0} + '@lerna-lite/init@5.0.0': + resolution: {integrity: sha512-nCZ2yG0ExTMG27dMZ2IeDzwbR9Wsq4P6ZbqGlGSLicCMRtU9VsvtveDI4Jtkhjn9WNaO6FFu8ktbwPiQ0i88Xw==} + engines: {node: ^22.17.0 || >=24.0.0} - '@lerna-lite/list@4.10.2': - resolution: {integrity: sha512-a2RuHz2rgFhTpIicv0zdt39ePXSAHbq+1AKLyJiANTz2vs9N8Y4WV0iBRG0cOudHnTceundBHc+9PaYVcOkbAA==} - engines: {node: ^20.17.0 || >=22.9.0} + '@lerna-lite/list@5.0.0': + resolution: {integrity: sha512-OQelKYKSqUvOdzLF7jPMj8SlE+2LASqkoxJhzjTPN05zRtz0lBD6VPERUpq9GYhlyNhg6vXBgyEUD+p5REjy/A==} + engines: {node: ^22.17.0 || >=24.0.0} - '@lerna-lite/listable@4.10.2': - resolution: {integrity: sha512-bnXm5Cd+QZ0brZkqt8pYWn/fxdGjz0g50tljZqN8lOpVF8VLrtqiaIOdob47TGxt3L68DL4FiBzIiSZNT93XqA==} - engines: {node: ^20.17.0 || >=22.9.0} + '@lerna-lite/listable@5.0.0': + resolution: {integrity: sha512-zpZUwX2NOM9pVziVM1uutVCFibxF2mWFcqT6anCITud2QN4ov6gSbKYiBLg8VcE7gZW8YlYw6Ib5eC2P1oZJfQ==} + engines: {node: ^22.17.0 || >=24.0.0} - '@lerna-lite/npmlog@4.10.0': - resolution: {integrity: sha512-vwI9qbhbbEjZJW/xXcOypqbIp3QXjsFD0kxGeHpGWXheeMtQSkRicJHH6v2dwVFid10EQmET47ItlCRAMhp12g==} - engines: {node: ^20.17.0 || >=22.9.0} + '@lerna-lite/npmlog@5.0.0': + resolution: {integrity: sha512-ZFgl88Las1pCBUoDsDFi9G1vCKXiauMCqrT2qkv3g7lxInvKRc0jGHG68W/NV7iMOD5JU5fhWRwBQmJ1DPsrbA==} + engines: {node: ^22.17.0 || >=24.0.0} - '@lerna-lite/version@4.10.2': - resolution: {integrity: sha512-Vp989fhidy2nULnOVhakaO2yqevUggcZmOWg6XoI8TV7lRDNPuxujMmWG71KNI4OSKeOq0aHrMR8XkyIYejwpw==} - engines: {node: ^20.17.0 || >=22.9.0} + '@lerna-lite/version@5.0.0': + resolution: {integrity: sha512-WfsCoRsrfQm1gYi6XKB+Yh5tvAGkL0PYBEEjo8hzfBO+YynxAyI1VWbZwI/fLNNZcDR9E8u7ceUE9B6gFbkGTA==} + engines: {node: ^22.17.0 || >=24.0.0} + + '@mapbox/node-pre-gyp@2.0.3': + resolution: {integrity: sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==} + engines: {node: '>=18'} + hasBin: true '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} @@ -2396,8 +2762,8 @@ packages: '@types/react': '>=16' react: '>=16' - '@modelcontextprotocol/sdk@1.24.3': - resolution: {integrity: sha512-YgSHW29fuzKKAHTGe9zjNoo+yF8KaQPzDC2W9Pv41E7/57IfY+AMGJ/aDFlgTLcVVELoggKE4syABCE75u3NCw==} + '@modelcontextprotocol/sdk@1.29.0': + resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} engines: {node: '>=18'} peerDependencies: '@cfworker/json-schema': ^4.1.1 @@ -2406,9 +2772,30 @@ packages: '@cfworker/json-schema': optional: true + '@module-federation/error-codes@0.22.0': + resolution: {integrity: sha512-xF9SjnEy7vTdx+xekjPCV5cIHOGCkdn3pIxo9vU7gEZMIw0SvAEdsy6Uh17xaCpm8V0FWvR0SZoK9Ik6jGOaug==} + + '@module-federation/runtime-core@0.22.0': + resolution: {integrity: sha512-GR1TcD6/s7zqItfhC87zAp30PqzvceoeDGYTgF3Vx2TXvsfDrhP6Qw9T4vudDQL3uJRne6t7CzdT29YyVxlgIA==} + + '@module-federation/runtime-tools@0.22.0': + resolution: {integrity: sha512-4ScUJ/aUfEernb+4PbLdhM/c60VHl698Gn1gY21m9vyC1Ucn69fPCA1y2EwcCB7IItseRMoNhdcWQnzt/OPCNA==} + + '@module-federation/runtime@0.22.0': + resolution: {integrity: sha512-38g5iPju2tPC3KHMPxRKmy4k4onNp6ypFPS1eKGsNLUkXgHsPMBFqAjDw96iEcjri91BrahG4XcdyKi97xZzlA==} + + '@module-federation/sdk@0.22.0': + resolution: {integrity: sha512-x4aFNBKn2KVQRuNVC5A7SnrSCSqyfIWmm1DvubjbO9iKFe7ith5niw8dqSFBekYBg2Fwy+eMg4sEFNVvCAdo6g==} + + '@module-federation/webpack-bundler-runtime@0.22.0': + resolution: {integrity: sha512-aM8gCqXu+/4wBmJtVeMeeMN5guw3chf+2i6HajKtQv7SJfxV/f4IyNQJUeUQu9HfiAZHjqtMV5Lvq/Lvh8LdyA==} + '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} + '@napi-rs/wasm-runtime@1.0.7': + resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} + '@noble/hashes@1.8.0': resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} @@ -2449,8 +2836,8 @@ packages: resolution: {integrity: sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==} engines: {node: ^20.17.0 || >=22.9.0} - '@npmcli/run-script@10.0.3': - resolution: {integrity: sha512-ER2N6itRkzWbbtVmZ9WKaWxVlKlOeBFF1/7xx+KA5J1xKa4JjUwBdb6tDpk0v1qA+d+VDwHI9qmLcXSWcmi+Rw==} + '@npmcli/run-script@10.0.4': + resolution: {integrity: sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==} engines: {node: ^20.17.0 || >=22.9.0} '@octokit/auth-token@6.0.0': @@ -2469,6 +2856,9 @@ packages: resolution: {integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==} engines: {node: '>= 20'} + '@octokit/openapi-types@24.2.0': + resolution: {integrity: sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==} + '@octokit/openapi-types@27.0.0': resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==} @@ -2493,6 +2883,10 @@ packages: peerDependencies: '@octokit/core': '>=6' + '@octokit/request-error@5.1.1': + resolution: {integrity: sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==} + engines: {node: '>= 18'} + '@octokit/request-error@7.1.0': resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} engines: {node: '>= 20'} @@ -2505,9 +2899,23 @@ packages: resolution: {integrity: sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==} engines: {node: '>= 20'} + '@octokit/types@13.10.0': + resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==} + '@octokit/types@16.0.0': resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} + '@octokit/webhooks-methods@4.1.0': + resolution: {integrity: sha512-zoQyKw8h9STNPqtm28UGOYFE7O6D4Il8VJwhAtMHFt2C4L0VQT1qGKLeefUOqHNs1mNRYSadVv7x0z8U2yyeWQ==} + engines: {node: '>= 18'} + + '@octokit/webhooks-types@7.6.1': + resolution: {integrity: sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw==} + + '@octokit/webhooks@12.3.2': + resolution: {integrity: sha512-exj1MzVXoP7xnAcAB3jZ97pTvVPkQF9y6GA/dvYC47HV7vLv+24XRS6b/v/XnyikpEuvMhugEXdGtAlU086WkQ==} + engines: {node: '>= 18'} + '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} @@ -2544,36 +2952,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.1': resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} @@ -2650,21 +3064,24 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - '@redocly/ajv@8.17.1': - resolution: {integrity: sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw==} + '@redocly/ajv@8.18.0': + resolution: {integrity: sha512-F+LMD2IDIXuHxgpLJh3nkLj9+tSaEzoUWd+7fONGq5pe2169FUDjpEkOfEpoGLz1sbZni/69p07OsecNfAOpqA==} + + '@redocly/ajv@8.18.3': + resolution: {integrity: sha512-l42u0of3hY98sN2A+M4qTX1O/KrpgGH32Hu9kP2GtHyD5Dfqq86PKFLe5dwaD8DEnNmlOlll2BAmeEtf0DaySg==} - '@redocly/config@0.22.2': - resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==} + '@redocly/config@0.46.1': + resolution: {integrity: sha512-dSdkB2wRLtvl3f7ayRu9vqVhUMjjRaxZlHgRbgOtPPXxn4uI/ciDO87h4CJb7Iet+OVpevpAU6gU8bo5qVbQxg==} - '@redocly/openapi-core@1.34.6': - resolution: {integrity: sha512-2+O+riuIUgVSuLl3Lyh5AplWZyVMNuG2F98/o6NrutKJfW4/GTZdPpZlIphS0HGgcOHgmWcCSHj+dWFlZaGSHw==} - engines: {node: '>=18.17.0', npm: '>=9.5.0'} + '@redocly/openapi-core@2.26.0': + resolution: {integrity: sha512-BjTPzSV1Gv430W9S/7i5T/dEZDK00GFk6ILCNTI+31pA9lEFJOXc0XRJT+V3v+m3nXIgGoo6GgqeLdAiM10rNg==} + engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} - '@reduxjs/toolkit@1.9.7': - resolution: {integrity: sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==} + '@reduxjs/toolkit@2.11.2': + resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==} peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 peerDependenciesMeta: react: optional: true @@ -2683,6 +3100,15 @@ packages: peerDependencies: rollup: ^1.20.0||^2.0.0 + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@rollup/rollup-android-arm-eabi@4.53.3': resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} cpu: [arm] @@ -2717,56 +3143,67 @@ packages: resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.53.3': resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.53.3': resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.53.3': resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.53.3': resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.53.3': resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.53.3': resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.53.3': resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.53.3': resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.53.3': resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.53.3': resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openharmony-arm64@4.53.3': resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} @@ -2793,6 +3230,74 @@ packages: cpu: [x64] os: [win32] + '@rspack/binding-darwin-arm64@1.7.11': + resolution: {integrity: sha512-oduECiZVqbO5zlVw+q7Vy65sJFth99fWPTyucwvLJJtJkPL5n17Uiql2cYP6Ijn0pkqtf1SXgK8WjiKLG5bIig==} + cpu: [arm64] + os: [darwin] + + '@rspack/binding-darwin-x64@1.7.11': + resolution: {integrity: sha512-a1+TtTE9ap6RalgFi7FGIgkJP6O4Vy6ctv+9WGJy53E4kuqHR0RygzaiVxCI/GMc/vBT9vY23hyrpWb3d1vtXA==} + cpu: [x64] + os: [darwin] + + '@rspack/binding-linux-arm64-gnu@1.7.11': + resolution: {integrity: sha512-P0QrGRPbTWu6RKWfN0bDtbnEps3rXH0MWIMreZABoUrVmNQKtXR6e73J3ub6a+di5s2+K0M2LJ9Bh2/H4UsDUA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rspack/binding-linux-arm64-musl@1.7.11': + resolution: {integrity: sha512-6ky7R43VMjWwmx3Yx7Jl7faLBBMAgMDt+/bN35RgwjiPgsIByz65EwytUVuW9rikB43BGHvA/eqlnjLrUzNBqw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rspack/binding-linux-x64-gnu@1.7.11': + resolution: {integrity: sha512-cuOJMfCOvb2Wgsry5enXJ3iT1FGUjdPqtGUBVupQlEG4ntSYsQ2PtF4wIDVasR3wdxC5nQbipOrDiN/u6fYsdQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rspack/binding-linux-x64-musl@1.7.11': + resolution: {integrity: sha512-CoK37hva4AmHGh3VCsQXmGr40L36m1/AdnN5LEjUX6kx5rEH7/1nEBN6Ii72pejqDVvk9anEROmPDiPw10tpFg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rspack/binding-wasm32-wasi@1.7.11': + resolution: {integrity: sha512-OtrmnPUVJMxjNa3eDMfHyPdtlLRmmp/aIm0fQHlAOATbZvlGm12q7rhPW5BXTu1yh+1rQ1/uqvz+SzKEZXuJaQ==} + cpu: [wasm32] + + '@rspack/binding-win32-arm64-msvc@1.7.11': + resolution: {integrity: sha512-lObFW6e5lCWNgTBNwT//yiEDbsxm9QG4BYUojqeXxothuzJ/L6ibXz6+gLMvbOvLGV3nKgkXmx8GvT9WDKR0mA==} + cpu: [arm64] + os: [win32] + + '@rspack/binding-win32-ia32-msvc@1.7.11': + resolution: {integrity: sha512-0pYGnZd8PPqNR68zQ8skamqNAXEA1sUfXuAdYcknIIRq2wsbiwFzIc0Pov1cIfHYab37G7sSIPBiOUdOWF5Ivw==} + cpu: [ia32] + os: [win32] + + '@rspack/binding-win32-x64-msvc@1.7.11': + resolution: {integrity: sha512-EeQXayoQk/uBkI3pdoXfQBXNIUrADq56L3s/DFyM2pJeUDrWmhfIw2UFIGkYPTMSCo8F2JcdcGM32FGJrSnU0Q==} + cpu: [x64] + os: [win32] + + '@rspack/binding@1.7.11': + resolution: {integrity: sha512-2MGdy2s2HimsDT444Bp5XnALzNRxuBNc7y0JzyuqKbHBywd4x2NeXyhWXXoxufaCFu5PBc9Qq9jyfjW2Aeh06Q==} + + '@rspack/core@1.7.11': + resolution: {integrity: sha512-rsD9b+Khmot5DwCMiB3cqTQo53ioPG3M/A7BySu8+0+RS7GCxKm+Z+mtsjtG/vsu4Tn2tcqCdZtA3pgLoJB+ew==} + engines: {node: '>=18.12.0'} + peerDependencies: + '@swc/helpers': '>=0.5.1' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@rspack/lite-tapable@1.1.0': + resolution: {integrity: sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==} + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -2808,14 +3313,27 @@ packages: '@sideway/pinpoint@2.0.0': resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + '@simple-git/args-pathspec@1.0.3': + resolution: {integrity: sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==} + + '@simple-git/argv-parser@1.1.0': + resolution: {integrity: sha512-sUKOu2lb5vGIWADNNLpscyj07DAeQZU3KLbnE2Tj53tW6BbDQKMly2CCfnR4oYzqtRELCPWfwaPg+Q0T8qfKBg==} + '@simple-libs/child-process-utils@1.0.1': resolution: {integrity: sha512-3nWd8irxvDI6v856wpPCHZ+08iQR0oHTZfzAZmnbsLzf+Sf1odraP6uKOHDZToXq3RPRV/LbqGVlSCogm9cJjg==} engines: {node: '>=18'} - '@simple-libs/stream-utils@1.1.0': - resolution: {integrity: sha512-6rsHTjodIn/t90lv5snQjRPVtOosM7Vp0AKdrObymq45ojlgVwnpAqdc+0OBBrpEiy31zZ6/TKeIVqV1HwvnuQ==} + '@simple-libs/hosted-git-info@1.0.2': + resolution: {integrity: sha512-aAmGQdMH+ZinytKuA2832u0ATeOFNYNk4meBEXtB5xaPotUgggYNhq5tYU/v17wEbmTW5P9iHNqNrFyrhnqBAg==} + engines: {node: '>=18'} + + '@simple-libs/stream-utils@1.2.0': + resolution: {integrity: sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==} engines: {node: '>=18'} + '@sinclair/typebox@0.25.24': + resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==} + '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -2830,10 +3348,6 @@ packages: resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} engines: {node: '>=14.16'} - '@sindresorhus/merge-streams@2.3.0': - resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} - engines: {node: '>=18'} - '@sindresorhus/merge-streams@4.0.0': resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} @@ -2844,6 +3358,17 @@ packages: '@sinonjs/fake-timers@13.0.5': resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==} + '@sinonjs/fake-timers@15.3.0': + resolution: {integrity: sha512-m2xozxSfCIxjDdvbhIWazlP2i2aha/iUmbl94alpsIbd3iLTfeXgfBVbwyWogB6l++istyGZqamgA/EcqYf+Bg==} + + '@slack/types@2.20.1': + resolution: {integrity: sha512-eWX2mdt1ktpn8+40iiMc404uGrih+2fxiky3zBcPjtXKj6HLRdYlmhrPkJi7JTJm8dpXR6BWVWEDBXtaWMKD6A==} + engines: {node: '>= 12.13.0', npm: '>= 6.12.0'} + + '@slack/webhook@7.0.8': + resolution: {integrity: sha512-x3gttyRJGZSeIm+juObzYsYEMa/weoeZUU3WDWwOCwmHhShSwtq1sj1jvuKx0mLNIA6VveVlc4zWvluhA6qcxg==} + engines: {node: '>= 18', npm: '>= 8.6.0'} + '@slorber/react-helmet-async@1.3.0': resolution: {integrity: sha512-e9/OK8VhwUSc67diWI8Rb3I0YgI9/SBQtnhe9aEuK6MhZm7ntZZimXgwXnd8W96YTmSOb9M4d8LwhRZyhWr/1A==} peerDependencies: @@ -2853,63 +3378,278 @@ packages: '@slorber/remark-comment@1.0.0': resolution: {integrity: sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==} - '@standard-schema/spec@1.0.0': - resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@smithy/chunked-blob-reader-native@4.2.3': + resolution: {integrity: sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==} + engines: {node: '>=18.0.0'} - '@stoplight/better-ajv-errors@1.0.3': - resolution: {integrity: sha512-0p9uXkuB22qGdNfy3VeEhxkU5uwvp/KrBTAbrLBURv6ilxIVwanKwjMc41lQfIVgPGcOkmLbTolfFrSsueu7zA==} - engines: {node: ^12.20 || >= 14.13} - peerDependencies: - ajv: '>=8' + '@smithy/chunked-blob-reader@5.2.2': + resolution: {integrity: sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw==} + engines: {node: '>=18.0.0'} - '@stoplight/json-ref-readers@1.2.2': - resolution: {integrity: sha512-nty0tHUq2f1IKuFYsLM4CXLZGHdMn+X/IwEUIpeSOXt0QjMUbL0Em57iJUDzz+2MkWG83smIigNZ3fauGjqgdQ==} - engines: {node: '>=8.3.0'} + '@smithy/config-resolver@4.4.14': + resolution: {integrity: sha512-N55f8mPEccpzKetUagdvmAy8oohf0J5cuj9jLI1TaSceRlq0pJsIZepY3kmAXAhyxqXPV6hDerDQhqQPKWgAoQ==} + engines: {node: '>=18.0.0'} - '@stoplight/json-ref-resolver@3.1.6': - resolution: {integrity: sha512-YNcWv3R3n3U6iQYBsFOiWSuRGE5su1tJSiX6pAPRVk7dP0L7lqCteXGzuVRQ0gMZqUl8v1P0+fAKxF6PLo9B5A==} - engines: {node: '>=8.3.0'} + '@smithy/core@3.23.14': + resolution: {integrity: sha512-vJ0IhpZxZAkFYOegMKSrxw7ujhhT2pass/1UEcZ4kfl5srTAqtPU5I7MdYQoreVas3204ykCiNhY1o7Xlz6Yyg==} + engines: {node: '>=18.0.0'} - '@stoplight/json@3.21.7': - resolution: {integrity: sha512-xcJXgKFqv/uCEgtGlPxy3tPA+4I+ZI4vAuMJ885+ThkTHFVkC+0Fm58lA9NlsyjnkpxFh4YiQWpH+KefHdbA0A==} - engines: {node: '>=8.3.0'} + '@smithy/credential-provider-imds@4.2.13': + resolution: {integrity: sha512-wboCPijzf6RJKLOvnjDAiBxGSmSnGXj35o5ZAWKDaHa/cvQ5U3ZJ13D4tMCE8JG4dxVAZFy/P0x/V9CwwdfULQ==} + engines: {node: '>=18.0.0'} - '@stoplight/ordered-object-literal@1.0.5': - resolution: {integrity: sha512-COTiuCU5bgMUtbIFBuyyh2/yVVzlr5Om0v5utQDgBCuQUOPgU1DwoffkTfg4UBQOvByi5foF4w4T+H9CoRe5wg==} - engines: {node: '>=8'} + '@smithy/eventstream-codec@4.2.13': + resolution: {integrity: sha512-vYahwBAtRaAcFbOmE9aLr12z7RiHYDSLcnogSdxfm7kKfsNa3wH+NU5r7vTeB5rKvLsWyPjVX8iH94brP7umiQ==} + engines: {node: '>=18.0.0'} - '@stoplight/path@1.3.2': - resolution: {integrity: sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ==} - engines: {node: '>=8'} + '@smithy/eventstream-serde-browser@4.2.13': + resolution: {integrity: sha512-wwybfcOX0tLqCcBP378TIU9IqrDuZq/tDV48LlZNydMpCnqnYr+hWBAYbRE+rFFf/p7IkDJySM3bgiMKP2ihPg==} + engines: {node: '>=18.0.0'} - '@stoplight/spectral-cli@6.15.0': - resolution: {integrity: sha512-FVeQIuqQQnnLfa8vy+oatTKUve7uU+3SaaAfdjpX/B+uB1NcfkKRJYhKT9wMEehDRaMPL5AKIRYMCFerdEbIpw==} - engines: {node: ^16.20 || ^18.18 || >= 20.17} - hasBin: true + '@smithy/eventstream-serde-config-resolver@4.3.13': + resolution: {integrity: sha512-ied1lO559PtAsMJzg2TKRlctLnEi1PfkNeMMpdwXDImk1zV9uvS/Oxoy/vcy9uv1GKZAjDAB5xT6ziE9fzm5wA==} + engines: {node: '>=18.0.0'} - '@stoplight/spectral-core@1.20.0': - resolution: {integrity: sha512-5hBP81nCC1zn1hJXL/uxPNRKNcB+/pEIHgCjPRpl/w/qy9yC9ver04tw1W0l/PMiv0UeB5dYgozXVQ4j5a6QQQ==} - engines: {node: ^16.20 || ^18.18 || >= 20.17} + '@smithy/eventstream-serde-node@4.2.13': + resolution: {integrity: sha512-hFyK+ORJrxAN3RYoaD6+gsGDQjeix8HOEkosoajvXYZ4VeqonM3G4jd9IIRm/sWGXUKmudkY9KdYjzosUqdM8A==} + engines: {node: '>=18.0.0'} - '@stoplight/spectral-formats@1.8.2': - resolution: {integrity: sha512-c06HB+rOKfe7tuxg0IdKDEA5XnjL2vrn/m/OVIIxtINtBzphZrOgtRn7epQ5bQF5SWp84Ue7UJWaGgDwVngMFw==} - engines: {node: ^16.20 || ^18.18 || >= 20.17} + '@smithy/eventstream-serde-universal@4.2.13': + resolution: {integrity: sha512-kRrq4EKLGeOxhC2CBEhRNcu1KSzNJzYY7RK3S7CxMPgB5dRrv55WqQOtRwQxQLC04xqORFLUgnDlc6xrNUULaA==} + engines: {node: '>=18.0.0'} - '@stoplight/spectral-formatters@1.5.0': - resolution: {integrity: sha512-lR7s41Z00Mf8TdXBBZQ3oi2uR8wqAtR6NO0KA8Ltk4FSpmAy0i6CKUmJG9hZQjanTnGmwpQkT/WP66p1GY3iXA==} - engines: {node: ^16.20 || ^18.18 || >= 20.17} + '@smithy/fetch-http-handler@5.3.16': + resolution: {integrity: sha512-nYDRUIvNd4mFmuXraRWt6w5UsZTNqtj4hXJA/iiOD4tuseIdLP9Lq38teH/SZTcIFCa2f+27o7hYpIsWktJKEQ==} + engines: {node: '>=18.0.0'} - '@stoplight/spectral-functions@1.10.1': - resolution: {integrity: sha512-obu8ZfoHxELOapfGsCJixKZXZcffjg+lSoNuttpmUFuDzVLT3VmH8QkPXfOGOL5Pz80BR35ClNAToDkdnYIURg==} - engines: {node: ^16.20 || ^18.18 || >= 20.17} + '@smithy/hash-blob-browser@4.2.14': + resolution: {integrity: sha512-rtQ5es8r/5v4rav7q5QTsfx9CtCyzrz/g7ZZZBH2xtMmd6G/KQrLOWfSHTvFOUPlVy59RQvxeBYJaLRoybMEyA==} + engines: {node: '>=18.0.0'} - '@stoplight/spectral-parsers@1.0.5': - resolution: {integrity: sha512-ANDTp2IHWGvsQDAY85/jQi9ZrF4mRrA5bciNHX+PUxPr4DwS6iv4h+FVWJMVwcEYdpyoIdyL+SRmHdJfQEPmwQ==} - engines: {node: ^16.20 || ^18.18 || >= 20.17} + '@smithy/hash-node@4.2.13': + resolution: {integrity: sha512-4/oy9h0jjmY80a2gOIo75iLl8TOPhmtx4E2Hz+PfMjvx/vLtGY4TMU/35WRyH2JHPfT5CVB38u4JRow7gnmzJA==} + engines: {node: '>=18.0.0'} - '@stoplight/spectral-ref-resolver@1.0.5': - resolution: {integrity: sha512-gj3TieX5a9zMW29z3mBlAtDOCgN3GEc1VgZnCVlr5irmR4Qi5LuECuFItAq4pTn5Zu+sW5bqutsCH7D4PkpyAA==} - engines: {node: ^16.20 || ^18.18 || >= 20.17} + '@smithy/hash-stream-node@4.2.13': + resolution: {integrity: sha512-WdQ7HwUjINXETeh6dqUeob1UHIYx8kAn9PSp1HhM2WWegiZBYVy2WXIs1lB07SZLan/udys9SBnQGt9MQbDpdg==} + engines: {node: '>=18.0.0'} + + '@smithy/invalid-dependency@4.2.13': + resolution: {integrity: sha512-jvC0RB/8BLj2SMIkY0Npl425IdnxZJxInpZJbu563zIRnVjpDMXevU3VMCRSabaLB0kf/eFIOusdGstrLJ8IDg==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/is-array-buffer@4.2.2': + resolution: {integrity: sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==} + engines: {node: '>=18.0.0'} + + '@smithy/md5-js@4.2.13': + resolution: {integrity: sha512-cNm7I9NXolFxtS20ojROddOEpSAeI1Obq6pd1Kj5HtHws3s9Fkk8DdHDfQSs5KuxCewZuVK6UqrJnfJmiMzDuQ==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-content-length@4.2.13': + resolution: {integrity: sha512-IPMLm/LE4AZwu6qiE8Rr8vJsWhs9AtOdySRXrOM7xnvclp77Tyh7hMs/FRrMf26kgIe67vFJXXOSmVxS7oKeig==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-endpoint@4.4.29': + resolution: {integrity: sha512-R9Q/58U+qBiSARGWbAbFLczECg/RmysRksX6Q8BaQEpt75I7LI6WGDZnjuC9GXSGKljEbA7N118LhGaMbfrTXw==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-retry@4.5.1': + resolution: {integrity: sha512-/zY+Gp7Qj2D2hVm3irkCyONER7E9MiX3cUUm/k2ZmhkzZkrPgwVS4aJ5NriZUEN/M0D1hhjrgjUmX04HhRwdWA==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-serde@4.2.17': + resolution: {integrity: sha512-0T2mcaM6v9W1xku86Dk0bEW7aEseG6KenFkPK98XNw0ZhOqOiD1MrMsdnQw9QsL3/Oa85T53iSMlm0SZdSuIEQ==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-stack@4.2.13': + resolution: {integrity: sha512-g72jN/sGDLyTanrCLH9fhg3oysO3f7tQa6eWWsMyn2BiYNCgjF24n4/I9wff/5XidFvjj9ilipAoQrurTUrLvw==} + engines: {node: '>=18.0.0'} + + '@smithy/node-config-provider@4.3.13': + resolution: {integrity: sha512-iGxQ04DsKXLckbgnX4ipElrOTk+IHgTyu0q0WssZfYhDm9CQWHmu6cOeI5wmWRxpXbBDhIIfXMWz5tPEtcVqbw==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.5.2': + resolution: {integrity: sha512-/oD7u8M0oj2ZTFw7GkuuHWpIxtWdLlnyNkbrWcyVYhd5RJNDuczdkb0wfnQICyNFrVPlr8YHOhamjNy3zidhmA==} + engines: {node: '>=18.0.0'} + + '@smithy/property-provider@4.2.13': + resolution: {integrity: sha512-bGzUCthxRmezuxkbu9wD33wWg9KX3hJpCXpQ93vVkPrHn9ZW6KNNdY5xAUWNuRCwQ+VyboFuWirG1lZhhkcyRQ==} + engines: {node: '>=18.0.0'} + + '@smithy/protocol-http@5.3.13': + resolution: {integrity: sha512-+HsmuJUF4u8POo6s8/a2Yb/AQ5t/YgLovCuHF9oxbocqv+SZ6gd8lC2duBFiCA/vFHoHQhoq7QjqJqZC6xOxxg==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-builder@4.2.13': + resolution: {integrity: sha512-tG4aOYFCZdPMjbgfhnIQ322H//ojujldp1SrHPHpBSb3NqgUp3dwiUGRJzie87hS1DYwWGqDuPaowoDF+rYCbQ==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-parser@4.2.13': + resolution: {integrity: sha512-hqW3Q4P+CDzUyQ87GrboGMeD7XYNMOF+CuTwu936UQRB/zeYn3jys8C3w+wMkDfY7CyyyVwZQ5cNFoG0x1pYmA==} + engines: {node: '>=18.0.0'} + + '@smithy/service-error-classification@4.2.13': + resolution: {integrity: sha512-a0s8XZMfOC/qpqq7RCPvJlk93rWFrElH6O++8WJKz0FqnA4Y7fkNi/0mnGgSH1C4x6MFsuBA8VKu4zxFrMe5Vw==} + engines: {node: '>=18.0.0'} + + '@smithy/shared-ini-file-loader@4.4.8': + resolution: {integrity: sha512-VZCZx2bZasxdqxVgEAhREvDSlkatTPnkdWy1+Kiy8w7kYPBosW0V5IeDwzDUMvWBt56zpK658rx1cOBFOYaPaw==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.3.13': + resolution: {integrity: sha512-YpYSyM0vMDwKbHD/JA7bVOF6kToVRpa+FM5ateEVRpsTNu564g1muBlkTubXhSKKYXInhpADF46FPyrZcTLpXg==} + engines: {node: '>=18.0.0'} + + '@smithy/smithy-client@4.12.9': + resolution: {integrity: sha512-ovaLEcTU5olSeHcRXcxV6viaKtpkHZumn6Ps0yn7dRf2rRSfy794vpjOtrWDO0d1auDSvAqxO+lyhERSXQ03EQ==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.14.0': + resolution: {integrity: sha512-OWgntFLW88kx2qvf/c/67Vno1yuXm/f9M7QFAtVkkO29IJXGBIg0ycEaBTH0kvCtwmvZxRujrgP5a86RvsXJAQ==} + engines: {node: '>=18.0.0'} + + '@smithy/url-parser@4.2.13': + resolution: {integrity: sha512-2G03yoboIRZlZze2+PT4GZEjgwQsJjUgn6iTsvxA02bVceHR6vp4Cuk7TUnPFWKF+ffNUk3kj4COwkENS2K3vw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-base64@4.3.2': + resolution: {integrity: sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-browser@4.2.2': + resolution: {integrity: sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-node@4.2.3': + resolution: {integrity: sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-buffer-from@4.2.2': + resolution: {integrity: sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==} + engines: {node: '>=18.0.0'} + + '@smithy/util-config-provider@4.2.2': + resolution: {integrity: sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-browser@4.3.45': + resolution: {integrity: sha512-ag9sWc6/nWZAuK3Wm9KlFJUnRkXLrXn33RFjIAmCTFThqLHY+7wCst10BGq56FxslsDrjhSie46c8OULS+BiIw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-node@4.2.49': + resolution: {integrity: sha512-jlN6vHwE8gY5AfiFBavtD3QtCX2f7lM3BKkz7nFKSNfFR5nXLXLg6sqXTJEEyDwtxbztIDBQCfjsGVXlIru2lQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-endpoints@3.3.4': + resolution: {integrity: sha512-BKoR/ubPp9KNKFxPpg1J28N1+bgu8NGAtJblBP7yHy8yQPBWhIAv9+l92SlQLpolGm71CVO+btB60gTgzT0wog==} + engines: {node: '>=18.0.0'} + + '@smithy/util-hex-encoding@4.2.2': + resolution: {integrity: sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-middleware@4.2.13': + resolution: {integrity: sha512-GTooyrlmRTqvUen4eK7/K1p6kryF7bnDfq6XsAbIsf2mo51B/utaH+XThY6dKgNCWzMAaH/+OLmqaBuLhLWRow==} + engines: {node: '>=18.0.0'} + + '@smithy/util-retry@4.3.1': + resolution: {integrity: sha512-FwmicpgWOkP5kZUjN3y+3JIom8NLGqSAJBeoIgK0rIToI817TEBHCrd0A2qGeKQlgDeP+Jzn4i0H/NLAXGy9uQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-stream@4.5.22': + resolution: {integrity: sha512-3H8iq/0BfQjUs2/4fbHZ9aG9yNzcuZs24LPkcX1Q7Z+qpqaGM8+qbGmE8zo9m2nCRgamyvS98cHdcWvR6YUsew==} + engines: {node: '>=18.0.0'} + + '@smithy/util-uri-escape@4.2.2': + resolution: {integrity: sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@4.2.2': + resolution: {integrity: sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-waiter@4.2.15': + resolution: {integrity: sha512-oUt9o7n8hBv3BL56sLSneL0XeigZSuem0Hr78JaoK33D9oKieyCvVP8eTSe3j7g2mm/S1DvzxKieG7JEWNJUNg==} + engines: {node: '>=18.0.0'} + + '@smithy/uuid@1.1.2': + resolution: {integrity: sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==} + engines: {node: '>=18.0.0'} + + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + + '@stoplight/better-ajv-errors@1.0.3': + resolution: {integrity: sha512-0p9uXkuB22qGdNfy3VeEhxkU5uwvp/KrBTAbrLBURv6ilxIVwanKwjMc41lQfIVgPGcOkmLbTolfFrSsueu7zA==} + engines: {node: ^12.20 || >= 14.13} + peerDependencies: + ajv: '>=8' + + '@stoplight/json-ref-readers@1.2.2': + resolution: {integrity: sha512-nty0tHUq2f1IKuFYsLM4CXLZGHdMn+X/IwEUIpeSOXt0QjMUbL0Em57iJUDzz+2MkWG83smIigNZ3fauGjqgdQ==} + engines: {node: '>=8.3.0'} + + '@stoplight/json-ref-resolver@3.1.6': + resolution: {integrity: sha512-YNcWv3R3n3U6iQYBsFOiWSuRGE5su1tJSiX6pAPRVk7dP0L7lqCteXGzuVRQ0gMZqUl8v1P0+fAKxF6PLo9B5A==} + engines: {node: '>=8.3.0'} + + '@stoplight/json@3.21.7': + resolution: {integrity: sha512-xcJXgKFqv/uCEgtGlPxy3tPA+4I+ZI4vAuMJ885+ThkTHFVkC+0Fm58lA9NlsyjnkpxFh4YiQWpH+KefHdbA0A==} + engines: {node: '>=8.3.0'} + + '@stoplight/ordered-object-literal@1.0.5': + resolution: {integrity: sha512-COTiuCU5bgMUtbIFBuyyh2/yVVzlr5Om0v5utQDgBCuQUOPgU1DwoffkTfg4UBQOvByi5foF4w4T+H9CoRe5wg==} + engines: {node: '>=8'} + + '@stoplight/path@1.3.2': + resolution: {integrity: sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ==} + engines: {node: '>=8'} + + '@stoplight/spectral-cli@6.15.0': + resolution: {integrity: sha512-FVeQIuqQQnnLfa8vy+oatTKUve7uU+3SaaAfdjpX/B+uB1NcfkKRJYhKT9wMEehDRaMPL5AKIRYMCFerdEbIpw==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} + hasBin: true + + '@stoplight/spectral-core@1.20.0': + resolution: {integrity: sha512-5hBP81nCC1zn1hJXL/uxPNRKNcB+/pEIHgCjPRpl/w/qy9yC9ver04tw1W0l/PMiv0UeB5dYgozXVQ4j5a6QQQ==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} + + '@stoplight/spectral-formats@1.8.2': + resolution: {integrity: sha512-c06HB+rOKfe7tuxg0IdKDEA5XnjL2vrn/m/OVIIxtINtBzphZrOgtRn7epQ5bQF5SWp84Ue7UJWaGgDwVngMFw==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} + + '@stoplight/spectral-formatters@1.5.0': + resolution: {integrity: sha512-lR7s41Z00Mf8TdXBBZQ3oi2uR8wqAtR6NO0KA8Ltk4FSpmAy0i6CKUmJG9hZQjanTnGmwpQkT/WP66p1GY3iXA==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} + + '@stoplight/spectral-functions@1.10.1': + resolution: {integrity: sha512-obu8ZfoHxELOapfGsCJixKZXZcffjg+lSoNuttpmUFuDzVLT3VmH8QkPXfOGOL5Pz80BR35ClNAToDkdnYIURg==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} + + '@stoplight/spectral-parsers@1.0.5': + resolution: {integrity: sha512-ANDTp2IHWGvsQDAY85/jQi9ZrF4mRrA5bciNHX+PUxPr4DwS6iv4h+FVWJMVwcEYdpyoIdyL+SRmHdJfQEPmwQ==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} + + '@stoplight/spectral-ref-resolver@1.0.5': + resolution: {integrity: sha512-gj3TieX5a9zMW29z3mBlAtDOCgN3GEc1VgZnCVlr5irmR4Qi5LuECuFItAq4pTn5Zu+sW5bqutsCH7D4PkpyAA==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} '@stoplight/spectral-ruleset-bundler@1.6.3': resolution: {integrity: sha512-AQFRO6OCKg8SZJUupnr3+OzI1LrMieDTEUHsYgmaRpNiDRPvzImE3bzM1KyQg99q58kTQyZ8kpr7sG8Lp94RRA==} @@ -3031,12 +3771,187 @@ packages: resolution: {integrity: sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==} engines: {node: '>=14'} + '@swc/core-darwin-arm64@1.15.24': + resolution: {integrity: sha512-uM5ZGfFXjtvtJ+fe448PVBEbn/CSxS3UAyLj3O9xOqKIWy3S6hPTXSPbszxkSsGDYKi+YFhzAsR4r/eXLxEQ0g==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.15.24': + resolution: {integrity: sha512-fMIb/Zfn929pw25VMBhV7Ji2Dl+lCWtUPNdYJQYOke+00E5fcQ9ynxtP8+qhUo/HZc+mYQb1gJxwHM9vty+lXg==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.15.24': + resolution: {integrity: sha512-vOkjsyjjxnoYx3hMEWcGxQrMgnNrRm6WAegBXrN8foHtDAR+zpdhpGF5a4lj1bNPgXAvmysjui8cM1ov/Clkaw==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.15.24': + resolution: {integrity: sha512-h/oNu+upkXJ6Cicnq7YGVj9PkdfarLCdQa8l/FlHYvfv8CEiMaeeTnpLU7gSBH/rGxosM6Qkfa/J9mThGF9CLA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@swc/core-linux-arm64-musl@1.15.24': + resolution: {integrity: sha512-ZpF/pRe1guk6sKzQI9D1jAORtjTdNlyeXn9GDz8ophof/w2WhojRblvSDJaGe7rJjcPN8AaOkhwdRUh7q8oYIg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@swc/core-linux-ppc64-gnu@1.15.24': + resolution: {integrity: sha512-QZEsZfisHTSJlmyChgDFNmKPb3W6Lhbfo/O76HhIngfEdnQNmukS38/VSe1feho+xkV5A5hETyCbx3sALBZKAQ==} + engines: {node: '>=10'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@swc/core-linux-s390x-gnu@1.15.24': + resolution: {integrity: sha512-DLdJKVsJgglqQrJBuoUYNmzm3leI7kUZhLbZGHv42onfKsGf6JDS3+bzCUQfte/XOqDjh/tmmn1DR/CF/tCJFw==} + engines: {node: '>=10'} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@swc/core-linux-x64-gnu@1.15.24': + resolution: {integrity: sha512-IpLYfposPA/XLxYOKpRfeccl1p5dDa3+okZDHHTchBkXEaVCnq5MADPmIWwIYj1tudt7hORsEHccG5no6IUQRw==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@swc/core-linux-x64-musl@1.15.24': + resolution: {integrity: sha512-JHy3fMSc0t/EPWgo74+OK5TGr51aElnzqfUPaiRf2qJ/BfX5CUCfMiWVBuhI7qmVMBnk1jTRnL/xZnOSHDPLYg==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@swc/core-win32-arm64-msvc@1.15.24': + resolution: {integrity: sha512-Txj+qUH1z2bUd1P3JvwByfjKFti3cptlAxhWgmunBUUxy/IW3CXLZ6l6Gk4liANadKkU71nIU1X30Z5vpMT3BA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.15.24': + resolution: {integrity: sha512-15D/nl3XwrhFpMv+MADFOiVwv3FvH9j8c6Rf8EXBT3Q5LoMh8YnDnSgPYqw1JzPnksvsBX6QPXLiPqmcR/Z4qQ==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.15.24': + resolution: {integrity: sha512-PR0PlTlPra2JbaDphrOAzm6s0v9rA0F17YzB+XbWD95B4g2cWcZY9LAeTa4xll70VLw9Jr7xBrlohqlQmelMFQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.15.24': + resolution: {integrity: sha512-5Hj8aNasue7yusUt8LGCUe/AjM7RMAce8ZoyDyiFwx7Al+GbYKL+yE7g4sJk8vEr1dKIkTRARkNIJENc4CjkBQ==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '>=0.5.17' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/html-darwin-arm64@1.15.24': + resolution: {integrity: sha512-2yH5kkeBM6mcSajWdIvh482HZDthvWM+SkH17CAzmgDgP2WGZ3IpdeIQxdV8Jj9kRdJaI0VqdXGT0qRRt6zw4A==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/html-darwin-x64@1.15.24': + resolution: {integrity: sha512-1k4Wl1eExT9yal3fX6MGcrpWOvYo+f7jnzw+ksg+8ifpYqpcrcy6Rv6cB78SgXzZJRpx8zBY1luk+zYyoDlrWA==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/html-linux-arm-gnueabihf@1.15.24': + resolution: {integrity: sha512-XbqWgyBE6tukUs+0zwzW+Xo3N/P6SoiJJ44QfB3RCb5Naz/1vwJbNgn9erFDgoq7CChmCooFuMfNnmh/E/Orsg==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/html-linux-arm64-gnu@1.15.24': + resolution: {integrity: sha512-GqJgkJHTlLM0tzJHX0tmU0ZAU4rIfMYZ2yJwCBwnFaLw4NacpimyWnWGJxH83SViVZ33DfLD2LG/dHN8xDAmRA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@swc/html-linux-arm64-musl@1.15.24': + resolution: {integrity: sha512-+7Xw69Y4p/LwhudMJZOQ++mKeXWTnh3vpNv5Ar+X1x8kfPBHKRXI3sRKf5JqE0oJqJXTgFP5xByzmO/KBee3sQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@swc/html-linux-ppc64-gnu@1.15.24': + resolution: {integrity: sha512-ZKxckgQkOY2a54jiCnIBs5TkMNx7zvuKbe1WsM/WV0BiTfMfw5iMmtCKAIuYCz/PJRXVK0dY4VH3DS7jabBvwg==} + engines: {node: '>=10'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@swc/html-linux-s390x-gnu@1.15.24': + resolution: {integrity: sha512-y0WBjqDZALqOzasxrEOlgHq6SX34nAE4+0MATufmSoFEdiQIBYkm9m4C8XQNCNHv52ERCu/EPGK3Q8RfXaBLhQ==} + engines: {node: '>=10'} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@swc/html-linux-x64-gnu@1.15.24': + resolution: {integrity: sha512-U//u302yBSgh6vFfJmrw17Xm7k9a17m/E3AcHK4w12CZOFtsKHQnxE3i9uFWhNbW5F70w2A9QENml5b0Us8XMg==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@swc/html-linux-x64-musl@1.15.24': + resolution: {integrity: sha512-U9gsAQCPiCROWKhLhSnW4JzkkOY6X4q0ZP/nA6UeKoahDdw4E8onPujtRSivt4ZxwdJKfAnsxeJY07V9YLZu9Q==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@swc/html-win32-arm64-msvc@1.15.24': + resolution: {integrity: sha512-AETh78z9ig4e1eAlx8a02BnIS5iNIJ7C43swQsxMraSDZvZuBxnvEXHqnt94jRlw7fzmJRRpJdVcInQ21u/xGA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/html-win32-ia32-msvc@1.15.24': + resolution: {integrity: sha512-ymJkEATvFF1+So41/SkulPBoRzRXP6HxUGfvdSJ29qeYejxWMrIWyjDE1+vAalo4IAR0cWFE2Ef2A2Qeg8QbGA==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/html-win32-x64-msvc@1.15.24': + resolution: {integrity: sha512-l+Gv0+jcSaDILljpEMC8pQE+ubRoZcft+woUgKTTlJQEFS+MgxKKLQjNCXx3hzhuru5/Yo8x71Ng/aVT7PwprA==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/html@1.15.24': + resolution: {integrity: sha512-2kWRCU09lBBg3bZLz8Kc37azQ6sBwiV1P7VDvqwKEJC2CtREe5y1XgLLd78kqSpFli52hZ6l3CNPDqkaX6ceAg==} + engines: {node: '>=14'} + + '@swc/types@0.1.26': + resolution: {integrity: sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==} + '@szmarczak/http-timer@5.0.1': resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} - '@testcontainers/postgresql@11.11.0': - resolution: {integrity: sha512-Og64I/h5LKLVvUTkAcLeTXfFcMhh3dCHCypN3Uzd+tQMd70SpCfQ0LCP9v/U+MS7JBRzU9EmqhUFkTOm4hyZWw==} + '@testcontainers/postgresql@11.13.0': + resolution: {integrity: sha512-Y+HLf+IEu9+h3MgxRhnFunw9ldk3jxN2PO6zyejN5AiDd1aSeBQ7hfpc/4MlMm65St2DdLGf39oE/vdW1+hc/Q==} '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} @@ -3046,8 +3961,8 @@ packages: resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} - '@testing-library/react@16.3.1': - resolution: {integrity: sha512-gr4KtAWqIOQoucWYD/f6ki+j5chXfcPc74Col/6poTyqTmn7zRmodWahWRCp8tYd+GMqBonw6hstNzqjbs6gjw==} + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} engines: {node: '>=18'} peerDependencies: '@testing-library/dom': ^10.0.0 @@ -3075,6 +3990,9 @@ packages: resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} + '@ts-morph/common@0.11.1': + resolution: {integrity: sha512-7hWZS0NRpEsNV8vWJzg7FEz6V8MaLNeJOmwmghqUXTpzk16V1LLZhdo+4QvE/+zv4cVci0OviuJFnqhEfoV3+g==} + '@tsconfig/node10@1.0.12': resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} @@ -3087,41 +4005,77 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - '@ttoss/config@1.35.12': - resolution: {integrity: sha512-anFVs+giW3Lcbmf7Ah+Ni0RbngDTAM0i20FRXJeGE+1DbMxPhxUSr5GKvNcGv/0keS3tZc38fQfM1C1uPgsLCw==} + '@ttoss/cloudformation@0.12.10': + resolution: {integrity: sha512-b0QCxfqBQ88VbCLMiDGbOvw4RmaJHQ9QSQEbXWDfzE1CLpsXAqDEPypP4d/30TqMKr6QHvlM8VyJ1SgR5+jNfg==} + + '@ttoss/config@1.37.8': + resolution: {integrity: sha512-Ikk44m4mM+xiuh4JaubdFCVtTKDRpM4lfZ2I/bU7uNJ9uGk+Vv7d+PxpYXwvb3aaNhTEHYfs4OMRnC4KSRyHmQ==} - '@ttoss/eslint-config@1.26.6': - resolution: {integrity: sha512-l6R7F0ToH8HpzcwoHqiX/Z7f5m1mbUHPFWDdFTI5jzPP534yumZIVbCjDADDS3l+cp5G8Nxjtiiu/c8RlywVhA==} + '@ttoss/eslint-config@1.26.14': + resolution: {integrity: sha512-r4XF6u0+WaqVApGxS5WFgViuOML8k+qU43s9UjGB8RIropCIRwjTAh9NM7XMl0hw/iGdIdqmnqs+NPt0seVMBQ==} peerDependencies: eslint: '>=9.20.0' - '@ttoss/http-server-mcp@0.3.2': - resolution: {integrity: sha512-RDnqoKm2UQpUOgt5BFvhepsevvNBLr0DKEzpUYQodHzVJFqAyWoGeCNpAIkujz9Bv5BHDdi1cbfSOTmbmu1sww==} + '@ttoss/http-server-mcp@0.11.1': + resolution: {integrity: sha512-PK9IjRfsGci0O5pQhBrozo3HWogEsWy+ej8+iUz21WJO4mUs3VKVhqqLnkSfcp9YDjxWuJleeL0w3Lj1MI38CA==} - '@ttoss/http-server@0.3.2': - resolution: {integrity: sha512-ghM10mkZc3P8hYG+Z9eKR3YGURf2/dZIvYIu0GLAvLL+s/AJlRZBEpEaJpcjimwzZo1hq6wtNkfHQBnOGLIOhA==} + '@ttoss/http-server@0.5.9': + resolution: {integrity: sha512-g3gB1UM1erVOFFIa5QA6H3czNteCIDVETzAWG2yVLvmQbvNcAe0OK5ZRdIYRD5z4aPV6r380C1798VM8soV5Qw==} '@ttoss/logger@0.7.1': resolution: {integrity: sha512-PxuFk1TvIRLxVYr5PXKLDsmmfTw+lV6Dl8IP6NA2JPgmT8J7EDjv5mAOTdJteIiUZwCPL2a1Z/rrYHU8pf2LPQ==} - '@ttoss/monorepo@1.28.0': - resolution: {integrity: sha512-kP6Kmi/YYbz/CvJzsNTSLHWakz+/SNyRUWiptvj9lfIOUrUKHiN/lKdKjeAfPyee9zpEYEwb48YDUpkV1NHrBg==} + '@ttoss/monorepo@1.29.8': + resolution: {integrity: sha512-Z24zgzFbtqJoqz7gr6XTQnBTMR1Mhl1GWZz6TKp68ZdLgporbqMqhKeAt9JhsK9AiuXYN1Zt2xJQPmGe6A3VkA==} hasBin: true - '@ttoss/postgresdb-cli@0.1.24': - resolution: {integrity: sha512-7PT1jvgu7ko2KOIBBQoEvsnnzzBN+ivfw3Z1DpucmQKPZAZ2sfpJis1nQxLKK1ktBlbX4XD9Y3UCmczYn2NOIg==} + '@ttoss/postgresdb-cli@0.2.8': + resolution: {integrity: sha512-X7bV26o4dipUerWqDjfOOU2akOibJcMP1TfqYlHTNXIc1DJPEHFNj+dZGE5bypenH3tCMrGs7nG+k0X6HUAb3Q==} hasBin: true - '@ttoss/postgresdb@0.3.0': - resolution: {integrity: sha512-YSNcS2ulsdbWJJkRC4S9ARy4dIR4U+wqT+kAVCb+HtMuqZRR38dZ5DmhYJ2DV0Q/eIgUplvFz9C2GMMDdTq7Gg==} + '@ttoss/postgresdb@0.8.0': + resolution: {integrity: sha512-icGG5SBGjWomyEXUJ/n4hUMVowyRSoGEGI4zpmJ/+pTGilel/vQYig+R8Sco1Lwq+rVwnfpC2yGCMzir3VZkVw==} - '@ttoss/test-utils@4.0.2': - resolution: {integrity: sha512-lmXG49ufbcM//SQtcaopzDw83HgjfAEzLgrarlRKDaKLZL1Yx/CEPCQvoG0T1z4kp3CpYhvsFEWgwBZ6ta4ttA==} + '@ttoss/read-config-file@2.2.8': + resolution: {integrity: sha512-Iw8RL0Pow5evda9iFUBexAZZ5Es9OhhEwhah32rKuCrgetHy7KohDvP6mXA1bMCx6AtPS8uKraGRAVicJ29+hg==} + + '@ttoss/test-utils@4.2.8': + resolution: {integrity: sha512-3Tw3miD59SZOQK3GQJwsmh5yd957zVauMB0wnHDH6L/nSELuhS9+T2uuLWXiO+pIt/GA025zjQDDGxvOgVmhrw==} peerDependencies: jest: ^30.0.0 react: '>=16.8.0' react-dom: '>=16.8.0' + '@turbo/darwin-64@2.9.4': + resolution: {integrity: sha512-ZSlPqJ5Vqg/wgVw8P3AOVCIosnbBilOxLq7TMz3MN/9U46DUYfdG2jtfevNDufyxyrg98pcPs/GBgDRaaids6g==} + cpu: [x64] + os: [darwin] + + '@turbo/darwin-arm64@2.9.4': + resolution: {integrity: sha512-9cjTWe4OiNlFMSRggPNh+TJlRs7MS5FWrHc96MOzft5vESWjjpvaadYPv5ykDW7b45mVHOF2U/W+48LoX9USWw==} + cpu: [arm64] + os: [darwin] + + '@turbo/linux-64@2.9.4': + resolution: {integrity: sha512-Cl1GjxqBXQ+r9KKowmXG+lhD1gclLp48/SE7NxL//66iaMytRw0uiphWGOkccD92iPiRjHLRUaA9lOTtgr5OCA==} + cpu: [x64] + os: [linux] + + '@turbo/linux-arm64@2.9.4': + resolution: {integrity: sha512-j2hPAKVmGNN2EsKigEWD+43y9m7zaPhNAs6ptsyfq0u7evHHBAXAwOfv86OEMg/gvC+pwGip0i1CIm1bR1vYug==} + cpu: [arm64] + os: [linux] + + '@turbo/windows-64@2.9.4': + resolution: {integrity: sha512-1jWPjCe9ZRmsDTXE7uzqfySNQspnUx0g6caqvwps+k/sc+fm9hC/4zRQKlXZLbVmP3Xxp601Ju71boegHdnYGw==} + cpu: [x64] + os: [win32] + + '@turbo/windows-arm64@2.9.4': + resolution: {integrity: sha512-dlko15TQVu/BFYmIY018Y3covWMRQlUgAkD+OOk+Rokcfj6VY02Vv4mCfT/Zns6B4q8jGbOd6IZhnCFYsE8Viw==} + cpu: [arm64] + os: [win32] + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -3143,15 +4097,16 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/bcryptjs@3.0.0': + resolution: {integrity: sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg==} + deprecated: This is a stub types definition. bcryptjs provides its own type definitions, so you do not need this installed. + '@types/body-parser@1.19.6': resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} '@types/bonjour@3.5.13': resolution: {integrity: sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==} - '@types/caseless@0.12.5': - resolution: {integrity: sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==} - '@types/co-body@6.1.3': resolution: {integrity: sha512-UhuhrQ5hclX6UJctv5m4Rfp52AfG9o9+d9/HwjxhVB5NjXxr5t9oKgJxN8xRHgr35oo8meUEHUPFWiKg6y71aA==} @@ -3161,9 +4116,6 @@ packages: '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - '@types/conventional-commits-parser@5.0.2': - resolution: {integrity: sha512-BgT2szDXnVypgpNxOK8aL5SGjUdaQbC++WZNjF1Qge3Og2+zhHj+RWhmehLhYyvQwqAmvezruVfOf8+3m74W+g==} - '@types/cookiejar@2.1.5': resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} @@ -3173,8 +4125,8 @@ packages: '@types/docker-modem@3.0.6': resolution: {integrity: sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==} - '@types/dockerode@3.3.47': - resolution: {integrity: sha512-ShM1mz7rCjdssXt7Xz0u1/R2BJC7piWa3SJpUBiVjCf2A3XNn4cP6pUVaD8bLanpPVVn4IKzJuw3dOvkJ8IbYw==} + '@types/dockerode@4.0.1': + resolution: {integrity: sha512-cmUpB+dPN955PxBEuXE3f6lKO1hHiIGYJA46IVF3BJpNsZGvtBDcRnlrHYHtOH/B6vtDOyl2kZ2ShAu3mgc27Q==} '@types/es-aggregate-error@1.0.6': resolution: {integrity: sha512-qJ7LIFp06h1QE1aVxbVd+zJP2wdaugYXYfd6JxsyRMrYHaxb6itXPogW2tz+ylUJ1n1b+JF1PHyYCfYHm0dvUg==} @@ -3200,11 +4152,8 @@ packages: '@types/express@4.17.25': resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==} - '@types/gtag.js@0.0.12': - resolution: {integrity: sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg==} - - '@types/hast@2.3.10': - resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} + '@types/gtag.js@0.0.20': + resolution: {integrity: sha512-wwAbk3SA2QeU67unN7zPxjEHmPmlXwZXZvQEpbEUQuMCRGgKyE1m6XDuTUA9b6pCGb/GqJmdfMOY5LuDjJSbbg==} '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -3212,11 +4161,6 @@ packages: '@types/history@4.7.11': resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==} - '@types/hoist-non-react-statics@3.3.7': - resolution: {integrity: sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==} - peerDependencies: - '@types/react': '*' - '@types/html-minifier-terser@6.1.0': resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==} @@ -3250,12 +4194,12 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/jsonwebtoken@9.0.10': + resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + '@types/markdown-escape@1.1.3': resolution: {integrity: sha512-JIc1+s3y5ujKnt/+N+wq6s/QdL2qZ11fP79MijrVXsAAnzSxCbT2j/3prHRouJdZ2yFLN3vkP0HytfnoCczjOw==} - '@types/mdast@3.0.15': - resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} - '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -3274,6 +4218,9 @@ packages: '@types/node-forge@1.3.14': resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==} + '@types/node@16.18.11': + resolution: {integrity: sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==} + '@types/node@17.0.45': resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} @@ -3283,8 +4230,8 @@ packages: '@types/node@22.19.3': resolution: {integrity: sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==} - '@types/node@25.0.3': - resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==} + '@types/node@25.5.2': + resolution: {integrity: sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -3299,30 +4246,21 @@ packages: resolution: {integrity: sha512-EULJ8LApcVEPbrfND0cRQqutIOdiIgJ1Mgrhpy755r14xMohPTEpkV/k28SJvuOs9bHRFW8x+KeDAEPiGQPB9Q==} deprecated: This is a stub types definition. parse-path provides its own type definitions, so you do not need this installed. - '@types/parse5@6.0.3': - resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} - - '@types/pg@8.16.0': - resolution: {integrity: sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==} + '@types/pg@8.20.0': + resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==} - '@types/picomatch@3.0.2': - resolution: {integrity: sha512-n0i8TD3UDB7paoMMxA3Y65vUncFJXjcUf7lQY7YyKGl6031FNjfsLs6pdLFCy2GNFxItPJG8GvvpbZc2skH7WA==} + '@types/picomatch@4.0.3': + resolution: {integrity: sha512-iG0T6+nYJ9FAPmx9SsUlnwcq1ZVRuCXcVEvWnntoPlrOpwtSTKNDC9uVAxTsC3PUvJ+99n4RpAcNgBbHX3JSnQ==} '@types/prismjs@1.26.5': resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} - '@types/prop-types@15.7.15': - resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} - '@types/qs@6.14.0': resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - '@types/react-redux@7.1.34': - resolution: {integrity: sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==} - '@types/react-relay@18.2.1': resolution: {integrity: sha512-KgmFapsxAylhxcFfaAv5GZZJhTHnDvV8IDZVsUm5afpJUvgZC1Y68ssfOGsFfiFY/2EhxHM/YPfpdKbfmF3Ecg==} @@ -3344,9 +4282,6 @@ packages: '@types/relay-test-utils@19.0.0': resolution: {integrity: sha512-yC/wVgDetV+88HrHLKlesIfju3RGsp32vLs0IiwBPKD0npQSTrFddcP3FNPe5Kkk4/Y/kgY3oR/DA8g8Bmmppw==} - '@types/request@2.48.13': - resolution: {integrity: sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==} - '@types/retry@0.12.2': resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} @@ -3386,8 +4321,8 @@ packages: '@types/superagent@8.1.9': resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==} - '@types/supertest@6.0.3': - resolution: {integrity: sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==} + '@types/supertest@7.2.0': + resolution: {integrity: sha512-uh2Lv57xvggst6lCqNdFAmDSvoMG7M/HDtX4iUCquxQ5EGPtaPM5PL5Hmi7LCvOG8db7YaCPNJEeoI8s/WzIQw==} '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} @@ -3401,8 +4336,8 @@ packages: '@types/urijs@1.19.26': resolution: {integrity: sha512-wkXrVzX5yoqLnndOwFsieJA7oKM8cNkOKJtf/3vVGSUFkWDKZvFHpIl9Pvqb/T9UsawBBFMTTD8xu7sK5MWuvg==} - '@types/uuid@10.0.0': - resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} '@types/validator@13.15.10': resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} @@ -3416,20 +4351,20 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/eslint-plugin@8.49.0': - resolution: {integrity: sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==} + '@typescript-eslint/eslint-plugin@8.58.0': + resolution: {integrity: sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.49.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/parser': ^8.58.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.49.0': - resolution: {integrity: sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==} + '@typescript-eslint/parser@8.58.0': + resolution: {integrity: sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/project-service@8.49.0': resolution: {integrity: sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==} @@ -3437,33 +4372,59 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/project-service@8.58.0': + resolution: {integrity: sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/scope-manager@8.49.0': resolution: {integrity: sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.58.0': + resolution: {integrity: sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.49.0': resolution: {integrity: sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.49.0': - resolution: {integrity: sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==} + '@typescript-eslint/tsconfig-utils@8.58.0': + resolution: {integrity: sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.58.0': + resolution: {integrity: sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/types@8.49.0': resolution: {integrity: sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.58.0': + resolution: {integrity: sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.49.0': resolution: {integrity: sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/typescript-estree@8.58.0': + resolution: {integrity: sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/utils@8.49.0': resolution: {integrity: sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3471,13 +4432,27 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/utils@8.58.0': + resolution: {integrity: sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/visitor-keys@8.49.0': resolution: {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.58.0': + resolution: {integrity: sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@unicode/unicode-17.0.0@1.6.16': + resolution: {integrity: sha512-advq5p36zZ+PDRUpDkWcHHR++R19kx0LYB5iG3bj0KB8mYVKg0ywS996e2bXeXxDb8XdOF7KTivcx7VkYie1pg==} + '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} cpu: [arm] @@ -3517,41 +4492,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -3573,10 +4556,64 @@ packages: cpu: [x64] os: [win32] + '@vercel/build-utils@9.1.0': + resolution: {integrity: sha512-ccknvdKH6LDB9ZzZaX8a8cOvFbI441APLHvKrunJE/wezY0skmfuEUK1qnfPApXMs4FMWzZQj2LO9qpzfgBPsQ==} + + '@vercel/error-utils@2.0.3': + resolution: {integrity: sha512-CqC01WZxbLUxoiVdh9B/poPbNpY9U+tO1N9oWHwTl5YAZxcqXmmWJ8KNMFItJCUUWdY3J3xv8LvAuQv2KZ5YdQ==} + + '@vercel/fun@1.1.2': + resolution: {integrity: sha512-n13RO1BUy8u6+kzDQ2++BRj4Y5EAiQPt+aV+Tb2HNTmToNr4Mu3dE1kFlaTVTxQzAT3hvIRlVEU/OMvF8LCFJw==} + engines: {node: '>= 16'} + + '@vercel/gatsby-plugin-vercel-analytics@1.0.11': + resolution: {integrity: sha512-iTEA0vY6RBPuEzkwUTVzSHDATo1aF6bdLLspI68mQ/BTbi5UQEGjpjyzdKOVcSYApDtFU6M6vypZ1t4vIEnHvw==} + + '@vercel/gatsby-plugin-vercel-builder@2.0.65': + resolution: {integrity: sha512-MQX56fuL4WHDhT/fvKy9FMJigOymTAcCqw8rteF1wpRBAGhapSJkhT34I4mkfRRMFk1kIV7ijwuX+w1mpRrLjA==} + + '@vercel/go@3.2.1': + resolution: {integrity: sha512-ezjmuUvLigH9V4egEaX0SZ+phILx8lb+Zkp1iTqKI+yl/ibPAtVo5o+dLSRAXU9U01LBmaLu3O8Oxd/JpWYCOw==} + + '@vercel/hydrogen@1.0.11': + resolution: {integrity: sha512-nkSQ0LC7rFRdfkTUGm9pIbAfRb2Aat05u8ouN0FoUl7/I/YVgd0G6iRBN9bOMFUIiBiaKB4KqaZEFzVfUHpwYw==} + + '@vercel/next@4.4.4': + resolution: {integrity: sha512-/xMzlOMY8UHzCehRZzx8TIdzVRCu3O2O+Gb7R8uRX0/ci9cLIjJvi0WfLyR06Ny4fMqMzzUuRADp5ezfJjaO1Q==} + + '@vercel/nft@0.27.10': + resolution: {integrity: sha512-zbaF9Wp/NsZtKLE4uVmL3FyfFwlpDyuymQM1kPbeT0mVOHKDQQNjnnfslB3REg3oZprmNFJuh3pkHBk2qAaizg==} + engines: {node: '>=16'} + hasBin: true + + '@vercel/node@5.0.4': + resolution: {integrity: sha512-AXpTFDzomabvi/FmxDDTwmnuqRBDfy2i0nzjKwVPM3ch94EucPbiAk3+18iZOX/A+o2mBO4jKc1DmB0ifQF2Rw==} + '@vercel/oidc@3.0.5': resolution: {integrity: sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw==} engines: {node: '>= 20'} + '@vercel/python@4.7.1': + resolution: {integrity: sha512-H4g/5e8unII4oQ+KN5IUvTZSzHmj+lLYDkAK15QGYgAxBtE/mHUvEZpPPo7DPUDIyfq8ybWB1bmk7H5kEahubQ==} + + '@vercel/redwood@2.1.13': + resolution: {integrity: sha512-e+4odfP2akWQq3WQ8mBkjqqwUcOvjhYmAhfg66IqTdIG15tIY6EOTMx/DhqXlvSDCyBbZPcqHb4/Xe662yPiEw==} + + '@vercel/remix-builder@5.1.1': + resolution: {integrity: sha512-OP1f6GI8MdylL4aUrX6n7OkN93jqmkWyLzQMeQMapVOXKvRFj05STZ4SQ/kNJkXdh3rEzjJWuCsJ6bklTHkJ7Q==} + + '@vercel/routing-utils@5.0.1': + resolution: {integrity: sha512-CH8sulzI8VNySWyJP+536fEX+oBnRuIVpw79jrn/0JwgCl7xb6E2JkKrMBT/mUCkZXh4vZZIOt23/QiIRK9Dyw==} + + '@vercel/ruby@2.2.0': + resolution: {integrity: sha512-FJF9gKVNHAljGOgV6zS5ou2N7ZgjOqMMtcPA5lsJEUI5/AZzVDWCmtcowTP80wEtHuupkd7d7M399FA082kXYQ==} + + '@vercel/static-build@2.5.43': + resolution: {integrity: sha512-r6Pi/yC1nUCuq6V7xDxfMKDkwla4qnqpJVohd7cTsWRDKlRzHJJX/YaDp/6yKrDaNH9UY6cBhj9ryL8QJWY63w==} + + '@vercel/static-config@3.0.0': + resolution: {integrity: sha512-2qtvcBJ1bGY0dYGYh3iM7yGKkk971FujLEDXzuW5wcZsPr1GSEjO/w2iSr3qve6nDDtBImsGoDEnus5FI4+fIw==} + '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -3628,9 +4665,9 @@ packages: '@xtuc/long@4.2.2': resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} - JSONStream@1.3.5: - resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} - hasBin: true + abbrev@3.0.1: + resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} + engines: {node: ^18.17.0 || >=20.5.0} abbrev@4.0.0: resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==} @@ -3648,6 +4685,11 @@ packages: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + acorn-import-phases@1.0.4: resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} engines: {node: '>=10.13.0'} @@ -3672,9 +4714,9 @@ packages: resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} engines: {node: '>= 10.0.0'} - agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} + adm-zip@0.5.17: + resolution: {integrity: sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==} + engines: {node: '>=12.0'} agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} @@ -3732,12 +4774,15 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ajv@8.11.0: - resolution: {integrity: sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==} + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ajv@8.6.3: + resolution: {integrity: sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==} + algoliasearch-helper@3.27.0: resolution: {integrity: sha512-eNYchRerbsvk2doHOMfdS1/B6Tm70oGtu8mzQlrNzbCeQ8p1MjCW8t/BL6iZ5PD+cL5NNMgTMyMnmiXZ1sgmNw==} peerDependencies: @@ -3753,10 +4798,6 @@ packages: ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -3811,6 +4852,14 @@ packages: resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} engines: {node: '>= 14'} + are-we-there-yet@4.0.2: + resolution: {integrity: sha512-ncSWAawFhKMJDTdoAeOV+jyW1VCMj5QIAwULIBV0SSR7B/RLPPEQiknKcg/RIIZlUQrxELpsxMiTUoAQ4sIUyg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + deprecated: This package is no longer supported. + + arg@4.1.0: + resolution: {integrity: sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==} + arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -3872,10 +4921,6 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} - arrify@2.0.1: - resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} - engines: {node: '>=8'} - as-table@1.0.55: resolution: {integrity: sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==} @@ -3900,17 +4945,22 @@ packages: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} - async-lock@1.4.1: - resolution: {integrity: sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==} + async-listen@1.2.0: + resolution: {integrity: sha512-CcEtRh/oc9Jc4uWeUwdpG/+Mb2YUHKmdaTf0gUr7Wa+bfp4xx70HOb3RuSTJMvqKNB1TkdTfjLdrcz2X4rkkZA==} + + async-listen@3.0.0: + resolution: {integrity: sha512-V+SsTpDqkrWTimiotsyl33ePSjA5/KrithwupuvJ6ztsqPvGv6ge4OredFhPffVXiLN/QUWvE0XcqJaYgt6fOg==} + engines: {node: '>= 14'} - async-retry@1.3.3: - resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} + async-listen@3.0.1: + resolution: {integrity: sha512-cWMaNwUJnf37C/S5TfCkk/15MwbPRwVYALA2jtjkbHjCmAPiDXyNJy2q3p1KAZzDLHAWyarUWSujUoHR4pEgrA==} + engines: {node: '>= 14'} - async@3.2.2: - resolution: {integrity: sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==} + async-lock@1.4.1: + resolution: {integrity: sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==} - async@3.2.4: - resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} + async-sema@3.1.1: + resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} @@ -3918,10 +4968,6 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - at-least-node@1.0.0: - resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} - engines: {node: '>= 4.0.0'} - author-regex@1.0.0: resolution: {integrity: sha512-KbWgR8wOYRAPekEmMXrYYdc7BRyhn2Ftk7KWfMUnQ43hFdojWEFRxhhRUm3/OFEdPa1r0KAvTTg9YQK57xTe0g==} engines: {node: '>=0.8'} @@ -3940,11 +4986,15 @@ packages: aws-sdk@2.1693.0: resolution: {integrity: sha512-cJmb8xEnVLT+R6fBS5sn/EFJiX7tUnDaPtOPZ1vFbOJtd0fnZn/Ky2XGgsvvoeliWeH7mL3TWSX5zXXGSQV6gQ==} engines: {node: '>= 10.0.0'} + deprecated: The AWS SDK for JavaScript (v2) has reached end-of-support, and no longer receives updates. Please migrate your code to use AWS SDK for JavaScript (v3). More info https://a.co/cUPnyil axe-core@4.11.0: resolution: {integrity: sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==} engines: {node: '>=4'} + axios@1.15.0: + resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==} + axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -3957,8 +5007,8 @@ packages: react-native-b4a: optional: true - babel-jest@30.2.0: - resolution: {integrity: sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==} + babel-jest@30.3.0: + resolution: {integrity: sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: '@babel/core': ^7.11.0 || ^8.0.0-0 @@ -3973,19 +5023,19 @@ packages: babel-plugin-dynamic-import-node@2.3.3: resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==} - babel-plugin-formatjs@10.5.41: - resolution: {integrity: sha512-ZpozYGek+Bdyl52LgzW1MhPYBRKbROdbHBuBz7KAO88Ht7GcyCBiwJxpAjVDb0YBA9LGKUemGQOLdEDkRCe2hg==} + babel-plugin-formatjs@11.3.2: + resolution: {integrity: sha512-/l7gaxOGyuHLk7sTNxsM4LXKy0rqa2S2hs5VemLEvGcyaCeUXVxdGxVgMxnh/dkoiOlx75nJKqgC914DEQ+FmQ==} babel-plugin-istanbul@7.0.1: resolution: {integrity: sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==} engines: {node: '>=12'} - babel-plugin-jest-hoist@30.2.0: - resolution: {integrity: sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==} + babel-plugin-jest-hoist@30.3.0: + resolution: {integrity: sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - babel-plugin-polyfill-corejs2@0.4.14: - resolution: {integrity: sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==} + babel-plugin-polyfill-corejs2@0.4.17: + resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -3994,8 +5044,13 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-regenerator@0.6.5: - resolution: {integrity: sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==} + babel-plugin-polyfill-corejs3@0.14.2: + resolution: {integrity: sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.8: + resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -4009,8 +5064,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 || ^8.0.0-0 - babel-preset-jest@30.2.0: - resolution: {integrity: sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==} + babel-preset-jest@30.3.0: + resolution: {integrity: sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: '@babel/core': ^7.11.0 || ^8.0.0-beta.1 @@ -4021,6 +5076,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + bare-events@2.8.2: resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} peerDependencies: @@ -4072,19 +5131,23 @@ packages: bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + bcryptjs@3.0.3: + resolution: {integrity: sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==} + hasBin: true + before-after-hook@4.0.0: resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} big.js@5.2.2: resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} - bignumber.js@9.3.1: - resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} - binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -4102,6 +5165,9 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} + boxen@6.2.1: resolution: {integrity: sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -4116,6 +5182,10 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -4128,6 +5198,9 @@ packages: bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-crc32@1.0.0: resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} engines: {node: '>=8.0.0'} @@ -4141,6 +5214,9 @@ packages: buffer@4.9.2: resolution: {integrity: sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==} + buffer@5.6.0: + resolution: {integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -4180,6 +5256,10 @@ packages: resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} engines: {node: '>= 0.8'} + bytes@3.1.0: + resolution: {integrity: sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==} + engines: {node: '>= 0.8'} + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -4240,13 +5320,13 @@ packages: caniuse-lite@1.0.30001760: resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} + carlin@1.48.3: + resolution: {integrity: sha512-SYWq5Q9WIz8vocxrS1BNKFn2u4gPAw/dseNwL5i7leawSsk+heXzvqlQdCYHqW9AhQmSS6rWdJ53aaoJO5eG4g==} + hasBin: true + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - chalk-template@1.1.2: - resolution: {integrity: sha512-2bxTP2yUH7AJj/VAXfcA+4IcWGdQ87HwBANLt5XxGTeomo8yG0y95N1um9i5StvhT/Bl0/2cARA5v1PpPXUxUA==} - engines: {node: '>=14.16'} - chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -4289,6 +5369,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.0: + resolution: {integrity: sha512-mxIojEAQcuEvT/lyXq+jf/3cO/KoA6z4CeNDGGevTybECPOMFCnQy3OPahluUkbqgPNGw5Bi78UC7Po6Lhy+NA==} + engines: {node: '>= 14.16.0'} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -4312,6 +5396,13 @@ packages: resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} engines: {node: '>=8'} + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} + engines: {node: '>=8'} + + cjs-module-lexer@1.2.3: + resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} + cjs-module-lexer@2.1.1: resolution: {integrity: sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==} @@ -4335,10 +5426,6 @@ packages: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} - cli-table3@0.6.5: resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} engines: {node: 10.* || >= 12.*} @@ -4370,10 +5457,6 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} - clsx@1.2.1: - resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} - engines: {node: '>=6'} - clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -4386,6 +5469,9 @@ packages: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + code-block-writer@10.1.1: + resolution: {integrity: sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw==} + collapse-white-space@2.1.0: resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} @@ -4399,6 +5485,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + colord@2.9.3: resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} @@ -4427,14 +5517,14 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} - commander@13.1.0: - resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} - engines: {node: '>=18'} - commander@14.0.2: resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} engines: {node: '>=20'} + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -4509,6 +5599,9 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + content-disposition@0.5.2: resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} engines: {node: '>= 0.6'} @@ -4521,33 +5614,33 @@ packages: resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} engines: {node: '>=18'} + content-type@1.0.4: + resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==} + engines: {node: '>= 0.6'} + content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} - conventional-changelog-angular@7.0.0: - resolution: {integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==} - engines: {node: '>=16'} - - conventional-changelog-angular@8.1.0: - resolution: {integrity: sha512-GGf2Nipn1RUCAktxuVauVr1e3r8QrLP/B0lEUsFktmGqc3ddbQkhoJZHJctVU829U1c6mTSWftrVOCHaL85Q3w==} + conventional-changelog-angular@8.3.1: + resolution: {integrity: sha512-6gfI3otXK5Ph5DfCOI1dblr+kN3FAm5a97hYoQkqNZxOaYa5WKfXH+AnpsmS+iUH2mgVC2Cg2Qw9m5OKcmNrIg==} engines: {node: '>=18'} - conventional-changelog-conventionalcommits@7.0.2: - resolution: {integrity: sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==} - engines: {node: '>=16'} + conventional-changelog-conventionalcommits@9.3.1: + resolution: {integrity: sha512-dTYtpIacRpcZgrvBYvBfArMmK2xvIpv2TaxM0/ZI5CBtNUzvF2x0t15HsbRABWprS6UPmvj+PzHVjSx4qAVKyw==} + engines: {node: '>=18'} conventional-changelog-preset-loader@5.0.0: resolution: {integrity: sha512-SetDSntXLk8Jh1NOAl1Gu5uLiCNSYenB5tm0YVeZKePRIgDW9lQImromTwLa3c/Gae298tsgOM+/CYT9XAl0NA==} engines: {node: '>=18'} - conventional-changelog-writer@8.2.0: - resolution: {integrity: sha512-Y2aW4596l9AEvFJRwFGJGiQjt2sBYTjPD18DdvxX9Vpz0Z7HQ+g1Z+6iYDAm1vR3QOJrDBkRHixHK/+FhkR6Pw==} + conventional-changelog-writer@8.4.0: + resolution: {integrity: sha512-HHBFkk1EECxxmCi4CTu091iuDpQv5/OavuCUAuZmrkWpmYfyD816nom1CvtfXJ/uYfAAjavgHvXHX291tSLK8g==} engines: {node: '>=18'} hasBin: true - conventional-changelog@7.1.1: - resolution: {integrity: sha512-rlqa8Lgh8YzT3Akruk05DR79j5gN9NCglHtJZwpi6vxVeaoagz+84UAtKQj/sT+RsfGaZkt3cdFCjcN6yjr5sw==} + conventional-changelog@7.2.0: + resolution: {integrity: sha512-BEdgG+vPl53EVlTTk9sZ96aagFp0AQ5pw/ggiQMy2SClLbTo1r0l+8dSg79gkLOO5DS1Lswuhp5fWn6RwE+ivg==} engines: {node: '>=18'} hasBin: true @@ -4555,13 +5648,8 @@ packages: resolution: {integrity: sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==} engines: {node: '>=18'} - conventional-commits-parser@5.0.0: - resolution: {integrity: sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==} - engines: {node: '>=16'} - hasBin: true - - conventional-commits-parser@6.2.1: - resolution: {integrity: sha512-20pyHgnO40rvfI0NGF/xiEoFMkXDtkF8FwHvk5BokoFoCuTQRI8vrNCNFWUOfuolKJMm1tPCHc8GgYEtr1XRNA==} + conventional-commits-parser@6.4.0: + resolution: {integrity: sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==} engines: {node: '>=18'} hasBin: true @@ -4570,6 +5658,10 @@ packages: engines: {node: '>=18'} hasBin: true + convert-hrtime@3.0.0: + resolution: {integrity: sha512-7V+KqSvMiHp8yWDuwfww06XleMWVVB9b9tURBx+G7UTADuo5hYPuowKloz4OzOqbPezxgo+fdQ1522WzPG4OeA==} + engines: {node: '>=8'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -4601,11 +5693,8 @@ packages: peerDependencies: webpack: ^5.1.0 - core-js-compat@3.47.0: - resolution: {integrity: sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==} - - core-js-pure@3.47.0: - resolution: {integrity: sha512-BcxeDbzUrRnXGYIVAGFtcGQVNpFcUhVjr6W7F8XktvQW2iJP9e66GP6xdKotCRFlrxBvNIBrhwKteRXqMV86Nw==} + core-js-compat@3.49.0: + resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} core-js@3.47.0: resolution: {integrity: sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==} @@ -4638,8 +5727,8 @@ packages: typescript: optional: true - cosmiconfig@9.0.0: - resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} + cosmiconfig@9.0.1: + resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==} engines: {node: '>=14'} peerDependencies: typescript: '>=4.9.5' @@ -4805,10 +5894,6 @@ packages: damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} - dargs@8.1.0: - resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==} - engines: {node: '>=12'} - data-uri-to-buffer@2.0.2: resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==} @@ -4847,6 +5932,15 @@ packages: supports-color: optional: true + debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -4874,9 +5968,21 @@ packages: babel-plugin-macros: optional: true + dedent@1.7.2: + resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + deep-equal@1.0.1: resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==} + deep-equal@2.2.3: + resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} + engines: {node: '>= 0.4'} + deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -4884,10 +5990,6 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - deepmerge-ts@7.1.5: - resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} - engines: {node: '>=16.0.0'} - deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -4942,6 +6044,9 @@ packages: resolution: {integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==} engines: {node: '>= 0.6.0'} + deprecation@2.3.1: + resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -4950,6 +6055,10 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-file@1.0.0: + resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} + engines: {node: '>=0.10.0'} + detect-indent@7.0.2: resolution: {integrity: sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A==} engines: {node: '>=12.20'} @@ -4959,6 +6068,10 @@ packages: engines: {node: '>=0.10'} hasBin: true + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -4985,10 +6098,6 @@ packages: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} - diff@5.2.0: - resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} - engines: {node: '>=0.3.1'} - dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -4997,8 +6106,8 @@ packages: resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==} engines: {node: '>=6'} - docker-compose@1.3.0: - resolution: {integrity: sha512-7Gevk/5eGD50+eMD+XDnFnOrruFkL0kSd7jEG4cjmqweDSUhB7i0g8is/nBdVpl+Bx338SqIB2GLKm32M+Vs6g==} + docker-compose@1.4.2: + resolution: {integrity: sha512-rPHigTKGaEHpkUmfd69QgaOp+Os5vGJwG/Ry8lcr8W/382AmI+z/D7qoa9BybKIkqNppaIbs8RYeHSevdQjWww==} engines: {node: '>= 6.0.0'} docker-modem@5.0.6: @@ -5013,13 +6122,13 @@ packages: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} - docusaurus-plugin-openapi-docs@4.5.1: - resolution: {integrity: sha512-3I6Sjz19D/eM86a24/nVkYfqNkl/zuXSP04XVo7qm/vlPeCpHVM4li2DLj7PzElr6dlS9RbaS4HVIQhEOPGBRQ==} + docusaurus-plugin-openapi-docs@5.0.0: + resolution: {integrity: sha512-G77fm6cu61VuwFMSOgy7KRHbATPNqNenO2VXuULAO8AmiAMVJ0fx9hTy+UDzr+U9pzCE5ZRJRPngf6F2klFaLA==} engines: {node: '>=14'} peerDependencies: - '@docusaurus/plugin-content-docs': ^3.5.0 - '@docusaurus/utils': ^3.5.0 - '@docusaurus/utils-validation': ^3.5.0 + '@docusaurus/plugin-content-docs': ^3.10.0 + '@docusaurus/utils': ^3.10.0 + '@docusaurus/utils-validation': ^3.10.0 react: ^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0 docusaurus-plugin-sass@0.2.6: @@ -5028,12 +6137,12 @@ packages: '@docusaurus/core': ^2.0.0-beta || ^3.0.0-alpha sass: ^1.30.0 - docusaurus-theme-openapi-docs@4.5.1: - resolution: {integrity: sha512-C7mYh9JC3l9jjRtqJVu0EIyOgxHB08jE0Tp5NSkNkrrBak4A13SrXCisNjvt1eaNjS+tsz7qD0bT3aI5hsRvWA==} + docusaurus-theme-openapi-docs@5.0.0: + resolution: {integrity: sha512-5JXaBUCopCFsq0rpcl79UFziHuj7+FhhYxuqwf0GsyDa513Ns7db3d5ULUgfUg1qehCj9jJ9JIQpZUQEnX7aJw==} engines: {node: '>=14'} peerDependencies: - '@docusaurus/theme-common': ^3.5.0 - docusaurus-plugin-openapi-docs: ^4.0.0 + '@docusaurus/theme-common': ^3.10.0 + docusaurus-plugin-openapi-docs: ^5.0.0 docusaurus-plugin-sass: ^0.2.3 react: ^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -5089,8 +6198,13 @@ packages: resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} engines: {node: '>=12'} + dotenv@17.4.1: + resolution: {integrity: sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==} + engines: {node: '>=12'} + dottie@2.0.6: resolution: {integrity: sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} @@ -5099,21 +6213,20 @@ packages: duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} - duplexify@4.1.3: - resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} - eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + edge-runtime@2.5.9: + resolution: {integrity: sha512-pk+k0oK0PVXdlT4oRp4lwh+unuKB7Ng4iZ2HB+EZ7QCEQizX360Rp/F4aRpgpRgdP2ufB35N+1KppHmYjqIGSg==} + engines: {node: '>=16'} + hasBin: true + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - effect@3.19.12: - resolution: {integrity: sha512-7F9RGTrCTC3D7nh9Zw+3VlJWwZgo5k33KA+476BAaD0rKIXKZsY/jQ+ipyhR/Avo239Fi6GqAVFs1mqM1IJ7yg==} - electron-to-chromium@1.5.267: resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} @@ -5121,10 +6234,6 @@ packages: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} engines: {node: '>=12'} - emoji-regex-xs@2.0.1: - resolution: {integrity: sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==} - engines: {node: '>=10.0.0'} - emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -5151,6 +6260,9 @@ packages: encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + end-of-stream@1.1.0: + resolution: {integrity: sha512-EoulkdKF/1xa92q25PbjuDcgJ9RDHYU2Rs3SCIvs2/dSQ3BpmxneNHmA/M7fe60M3PrV7nNGTTNbkK62l6vXiQ==} + end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} @@ -5158,10 +6270,6 @@ packages: resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} engines: {node: '>=10.13.0'} - enquirer@2.4.1: - resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} - engines: {node: '>=8.6'} - entities@2.2.0: resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} @@ -5203,10 +6311,16 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-get-iterator@1.1.3: + resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} + es-iterator-helpers@1.2.2: resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==} engines: {node: '>= 0.4'} + es-module-lexer@1.4.1: + resolution: {integrity: sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==} + es-module-lexer@2.0.0: resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} @@ -5235,11 +6349,141 @@ packages: esast-util-from-js@2.0.1: resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} + esbuild-android-64@0.14.47: + resolution: {integrity: sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + esbuild-android-arm64@0.14.47: + resolution: {integrity: sha512-OkwOjj7ts4lBp/TL6hdd8HftIzOy/pdtbrNA4+0oVWgGG64HrdVzAF5gxtJufAPOsEjkyh1oIYvKAUinKKQRSQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + esbuild-darwin-64@0.14.47: + resolution: {integrity: sha512-R6oaW0y5/u6Eccti/TS6c/2c1xYTb1izwK3gajJwi4vIfNs1s8B1dQzI1UiC9T61YovOQVuePDcfqHLT3mUZJA==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + esbuild-darwin-arm64@0.14.47: + resolution: {integrity: sha512-seCmearlQyvdvM/noz1L9+qblC5vcBrhUaOoLEDDoLInF/VQ9IkobGiLlyTPYP5dW1YD4LXhtBgOyevoIHGGnw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + esbuild-freebsd-64@0.14.47: + resolution: {integrity: sha512-ZH8K2Q8/Ux5kXXvQMDsJcxvkIwut69KVrYQhza/ptkW50DC089bCVrJZZ3sKzIoOx+YPTrmsZvqeZERjyYrlvQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + esbuild-freebsd-arm64@0.14.47: + resolution: {integrity: sha512-ZJMQAJQsIOhn3XTm7MPQfCzEu5b9STNC+s90zMWe2afy9EwnHV7Ov7ohEMv2lyWlc2pjqLW8QJnz2r0KZmeAEQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + esbuild-linux-32@0.14.47: + resolution: {integrity: sha512-FxZOCKoEDPRYvq300lsWCTv1kcHgiiZfNrPtEhFAiqD7QZaXrad8LxyJ8fXGcWzIFzRiYZVtB3ttvITBvAFhKw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + esbuild-linux-64@0.14.47: + resolution: {integrity: sha512-nFNOk9vWVfvWYF9YNYksZptgQAdstnDCMtR6m42l5Wfugbzu11VpMCY9XrD4yFxvPo9zmzcoUL/88y0lfJZJJw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + esbuild-linux-arm64@0.14.47: + resolution: {integrity: sha512-ywfme6HVrhWcevzmsufjd4iT3PxTfCX9HOdxA7Hd+/ZM23Y9nXeb+vG6AyA6jgq/JovkcqRHcL9XwRNpWG6XRw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + esbuild-linux-arm@0.14.47: + resolution: {integrity: sha512-ZGE1Bqg/gPRXrBpgpvH81tQHpiaGxa8c9Rx/XOylkIl2ypLuOcawXEAo8ls+5DFCcRGt/o3sV+PzpAFZobOsmA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + esbuild-linux-mips64le@0.14.47: + resolution: {integrity: sha512-mg3D8YndZ1LvUiEdDYR3OsmeyAew4MA/dvaEJxvyygahWmpv1SlEEnhEZlhPokjsUMfRagzsEF/d/2XF+kTQGg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + esbuild-linux-ppc64le@0.14.47: + resolution: {integrity: sha512-WER+f3+szmnZiWoK6AsrTKGoJoErG2LlauSmk73LEZFQ/iWC+KhhDsOkn1xBUpzXWsxN9THmQFltLoaFEH8F8w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + esbuild-linux-riscv64@0.14.47: + resolution: {integrity: sha512-1fI6bP3A3rvI9BsaaXbMoaOjLE3lVkJtLxsgLHqlBhLlBVY7UqffWBvkrX/9zfPhhVMd9ZRFiaqXnB1T7BsL2g==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + esbuild-linux-s390x@0.14.47: + resolution: {integrity: sha512-eZrWzy0xFAhki1CWRGnhsHVz7IlSKX6yT2tj2Eg8lhAwlRE5E96Hsb0M1mPSE1dHGpt1QVwwVivXIAacF/G6mw==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + esbuild-netbsd-64@0.14.47: + resolution: {integrity: sha512-Qjdjr+KQQVH5Q2Q1r6HBYswFTToPpss3gqCiSw2Fpq/ua8+eXSQyAMG+UvULPqXceOwpnPo4smyZyHdlkcPppQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + esbuild-openbsd-64@0.14.47: + resolution: {integrity: sha512-QpgN8ofL7B9z8g5zZqJE+eFvD1LehRlxr25PBkjyyasakm4599iroUpaj96rdqRlO2ShuyqwJdr+oNqWwTUmQw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + esbuild-sunos-64@0.14.47: + resolution: {integrity: sha512-uOeSgLUwukLioAJOiGYm3kNl+1wJjgJA8R671GYgcPgCx7QR73zfvYqXFFcIO93/nBdIbt5hd8RItqbbf3HtAQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + esbuild-windows-32@0.14.47: + resolution: {integrity: sha512-H0fWsLTp2WBfKLBgwYT4OTfFly4Im/8B5f3ojDv1Kx//kiubVY0IQunP2Koc/fr/0wI7hj3IiBDbSrmKlrNgLQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + esbuild-windows-64@0.14.47: + resolution: {integrity: sha512-/Pk5jIEH34T68r8PweKRi77W49KwanZ8X6lr3vDAtOlH5EumPE4pBHqkCUdELanvsT14yMXLQ/C/8XPi1pAtkQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + esbuild-windows-arm64@0.14.47: + resolution: {integrity: sha512-HFSW2lnp62fl86/qPQlqw6asIwCnEsEoNIL1h2uVMgakddf+vUuMcCbtUY1i8sst7KkgHrVKCJQB33YhhOweCQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + esbuild@0.14.47: + resolution: {integrity: sha512-wI4ZiIfFxpkuxB8ju4MHrGwGLyp1+awEHAHVpx6w7a+1pmYIq8T9FGEVVwFo0iFierDoMj++Xq69GXWYn2EiwA==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.27.1: resolution: {integrity: sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==} engines: {node: '>=18'} hasBin: true + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -5273,8 +6517,8 @@ packages: peerDependencies: eslint: '>=7.0.0' - eslint-config-turbo@2.6.3: - resolution: {integrity: sha512-HS6aanr+Cg4X1Ss8AObgdsa9LvSi1rHAU7sHWqD4MWKe+/4uPt0Zqt6VqX1QMIJI6bles+QxSpMPnahMN9hPLg==} + eslint-config-turbo@2.9.4: + resolution: {integrity: sha512-Mbkr1xA22hbp2FUFKt1hEUhTN858wndXbk/h1NdKcvfqn4gxJQoUHyEBDONN0KxgXEhWHl3Le3W4EM6TraN4qw==} peerDependencies: eslint: '>6.6.0' turbo: '>2.0.0' @@ -5325,10 +6569,10 @@ packages: eslint-import-resolver-webpack: optional: true - eslint-plugin-formatjs@5.4.2: - resolution: {integrity: sha512-IdJt/il0FASmk/aJDzl96Zh0tovm+KVhCbA5d+YC14gOpeFe1n6766JMi/RP9YOY9dhe6BbWEJnk9dPJwMMngw==} + eslint-plugin-formatjs@6.4.4: + resolution: {integrity: sha512-ggmS9LhU2D7+RJEE+c6yQmxjE1yqU+/CfSxu2aVQQ6dVqW+dC5QpCOogCLVk3EkhWKF99eEUeG4b8HhImDSqnA==} peerDependencies: - eslint: ^9.23.0 + eslint: 9 || 10 eslint-plugin-import@2.32.0: resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} @@ -5350,18 +6594,21 @@ packages: '@testing-library/dom': optional: true - eslint-plugin-jest@29.5.0: - resolution: {integrity: sha512-DAi9H8xN/TUuNOt+xDP1RqpCJLsSxBb5u1zXSpCyp0VAWGL8MBAg5t7/Dk+76iX7d1LhWu4DDH77IQNUolLDyg==} + eslint-plugin-jest@29.15.1: + resolution: {integrity: sha512-6BjyErCQauz3zfJvzLw/kAez2lf4LEpbHLvWBfEcG4EI0ZiRSwjoH2uZulMouU8kRkBH+S0rhqn11IhTvxKgKw==} engines: {node: ^20.12.0 || ^22.0.0 || >=24.0.0} peerDependencies: '@typescript-eslint/eslint-plugin': ^8.0.0 - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 jest: '*' + typescript: '>=4.8.4 <7.0.0' peerDependenciesMeta: '@typescript-eslint/eslint-plugin': optional: true jest: optional: true + typescript: + optional: true eslint-plugin-jsx-a11y@6.10.2: resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} @@ -5375,8 +6622,8 @@ packages: peerDependencies: eslint: '>=9.17.0' - eslint-plugin-prettier@5.5.4: - resolution: {integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==} + eslint-plugin-prettier@5.5.5: + resolution: {integrity: sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: '@types/eslint': '>=8.0.0' @@ -5400,11 +6647,10 @@ packages: peerDependencies: eslint: '>=0.8.0' - eslint-plugin-react-refresh@0.4.25: - resolution: {integrity: sha512-dRUD2LOdEqI4zXHqbQ442blQAzdSuShAaiSq5Vtyy6LT08YUf0oOjBDo4VPx0dCPgiPWh1WB4dtbLOd0kOlDPQ==} - deprecated: This version introduced false positive. This was reverted in 0.4.26 + eslint-plugin-react-refresh@0.5.2: + resolution: {integrity: sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==} peerDependencies: - eslint: '>=8.40' + eslint: ^9 || ^10 eslint-plugin-react@7.37.5: resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} @@ -5420,14 +6666,14 @@ packages: peerDependencies: eslint: '>=5.0.0' - eslint-plugin-turbo@2.6.3: - resolution: {integrity: sha512-91WZ+suhT/pk+qNS0/rqT43xLUlUblsa3a8jKmAStGhkJCmR2uX0oWo/e0Edb+It8MdnteXuYpCkvsK4Vw8FtA==} + eslint-plugin-turbo@2.9.4: + resolution: {integrity: sha512-8gIBw+QC7jLOjYLthoLYcDGf0pEefAukkMAl60exv3HkS3NO7AuXzzFjY3iDGDc4s5mbkC6f/692DCdb3XHwMg==} peerDependencies: eslint: '>6.6.0' turbo: '>2.0.0' - eslint-plugin-unicorn@62.0.0: - resolution: {integrity: sha512-HIlIkGLkvf29YEiS/ImuDZQbP12gWyx5i3C6XrRxMvVdqMroCI9qoVYCoIl17ChN+U89pn9sVwLxhIWj5nEc7g==} + eslint-plugin-unicorn@64.0.0: + resolution: {integrity: sha512-rNZwalHh8i0UfPlhNwg5BTUO1CMdKNmjqe+TgzOTZnpKoi8VBgsW7u9qCHIdpxEzZ1uwrJrPF0uRb7l//K38gA==} engines: {node: ^20.10.0 || >=21.0.0} peerDependencies: eslint: '>=9.38.0' @@ -5448,6 +6694,10 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint@9.39.2: resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5539,6 +6789,9 @@ packages: eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + events-intercept@2.0.0: + resolution: {integrity: sha512-blk1va0zol9QOrdZt0rFXo5KMkNPVSp92Eju/Qz8THwKWKRKeE0T8Br/1aW6+Edkyq9xHYgYxn2QtOnUKPUp+Q==} + events-universal@1.0.1: resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} @@ -5558,6 +6811,10 @@ packages: resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} engines: {node: '>=18.0.0'} + execa@3.2.0: + resolution: {integrity: sha512-kJJfVbI/lZE1PZYDI5VPxp8zXPO9rtxOkhpZ0jMKha56AI9y2gGVC6bkukStQf0ka5Rh15BA5m7cCCH4jmHqkw==} + engines: {node: ^8.12.0 || >=9.7.0} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -5573,15 +6830,23 @@ packages: resolution: {integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==} engines: {node: '>= 0.8.0'} + expand-tilde@2.0.2: + resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} + engines: {node: '>=0.10.0'} + expect@30.2.0: resolution: {integrity: sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + expect@30.3.0: + resolution: {integrity: sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + exponential-backoff@3.1.3: resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} - express-rate-limit@7.5.1: - resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} + express-rate-limit@8.3.2: + resolution: {integrity: sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==} engines: {node: '>= 16'} peerDependencies: express: '>= 4.11' @@ -5601,10 +6866,6 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - fast-check@3.23.2: - resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} - engines: {node: '>=8.0.0'} - fast-content-type-parse@3.0.0: resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} @@ -5646,8 +6907,14 @@ packages: fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fast-xml-parser@4.5.3: - resolution: {integrity: sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==} + fast-wrap-ansi@0.2.0: + resolution: {integrity: sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==} + + fast-xml-builder@1.1.4: + resolution: {integrity: sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==} + + fast-xml-parser@5.5.8: + resolution: {integrity: sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==} hasBin: true fastq@1.19.1: @@ -5672,6 +6939,9 @@ packages: fd-package-json@2.0.0: resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==} + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -5710,6 +6980,9 @@ packages: resolution: {integrity: sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==} engines: {node: '>=0.10.0'} + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -5742,9 +7015,9 @@ packages: resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - find-up@7.0.0: - resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} - engines: {node: '>=18'} + findup-sync@5.0.0: + resolution: {integrity: sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==} + engines: {node: '>= 10.13.0'} fix-dts-default-cjs-exports@1.0.1: resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} @@ -5784,10 +7057,6 @@ packages: resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} engines: {node: '>= 14.17'} - form-data@2.5.5: - resolution: {integrity: sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==} - engines: {node: '>= 0.12'} - form-data@4.0.5: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} @@ -5822,13 +7091,20 @@ packages: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} - fs-extra@11.3.2: - resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} + fs-extra@11.1.0: + resolution: {integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==} engines: {node: '>=14.14'} - fs-extra@9.1.0: - resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} - engines: {node: '>=10'} + fs-extra@11.3.4: + resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} + engines: {node: '>=14.14'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fs-minipass@1.2.7: + resolution: {integrity: sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==} fs-minipass@3.0.3: resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} @@ -5852,18 +7128,19 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - gaxios@6.7.1: - resolution: {integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==} - engines: {node: '>=14'} - - gcp-metadata@6.1.1: - resolution: {integrity: sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==} - engines: {node: '>=14'} + gauge@5.0.2: + resolution: {integrity: sha512-pMaFftXPtiGIHCJHdcUUx9Rby/rFT/Kkt3fIIGCs+9PMDIljSyRiqraTlxNtBReJRDfUefpa263RQ3vnp5G/LQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + deprecated: This package is no longer supported. generator-function@2.0.1: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} + generic-pool@3.4.2: + resolution: {integrity: sha512-H7cUpwCQSiJmAHM4c/aFu6fUfrhWXW1ncyh8ftxEPMu6AiYkHw9K8br720TGPZJbk5eOH2bynjZD1yPvdDAmag==} + engines: {node: '>= 4'} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -5898,6 +7175,10 @@ packages: get-source@2.0.12: resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==} + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -5913,9 +7194,9 @@ packages: get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} - git-raw-commits@4.0.0: - resolution: {integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==} - engines: {node: '>=16'} + git-raw-commits@5.0.1: + resolution: {integrity: sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==} + engines: {node: '>=18'} hasBin: true git-up@8.1.1: @@ -5946,6 +7227,13 @@ packages: glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + glob@11.1.0: + resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} + engines: {node: 20 || >=22} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@13.0.0: @@ -5954,7 +7242,7 @@ packages: glob@7.2.0: resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} @@ -5968,12 +7256,20 @@ packages: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} engines: {node: '>=10'} + global-modules@1.0.0: + resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==} + engines: {node: '>=0.10.0'} + + global-prefix@1.0.2: + resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==} + engines: {node: '>=0.10.0'} + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@16.5.0: - resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + globals@17.4.0: + resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==} engines: {node: '>=18'} globalthis@1.0.4: @@ -5988,18 +7284,6 @@ packages: resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - globby@14.1.0: - resolution: {integrity: sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==} - engines: {node: '>=18'} - - google-auth-library@9.15.1: - resolution: {integrity: sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==} - engines: {node: '>=14'} - - google-logging-utils@0.0.2: - resolution: {integrity: sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==} - engines: {node: '>=14'} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -6031,10 +7315,6 @@ packages: resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} engines: {node: '>=6.0'} - gtoken@7.1.0: - resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} - engines: {node: '>=14.0.0'} - gzip-size@6.0.0: resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} engines: {node: '>=10'} @@ -6084,21 +7364,12 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - hast-util-from-parse5@7.1.2: - resolution: {integrity: sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==} - hast-util-from-parse5@8.0.3: resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} - hast-util-parse-selector@3.1.1: - resolution: {integrity: sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==} - hast-util-parse-selector@4.0.0: resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} - hast-util-raw@7.2.3: - resolution: {integrity: sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==} - hast-util-raw@9.1.0: resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} @@ -6108,21 +7379,12 @@ packages: hast-util-to-jsx-runtime@2.3.6: resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} - hast-util-to-parse5@7.1.0: - resolution: {integrity: sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==} - hast-util-to-parse5@8.0.1: resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} - hast-util-whitespace@2.0.1: - resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} - hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} - hastscript@7.2.0: - resolution: {integrity: sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==} - hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} @@ -6142,9 +7404,13 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - hosted-git-info@7.0.2: - resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} - engines: {node: ^16.14.0 || >=18.0.0} + homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} + + hono@4.12.12: + resolution: {integrity: sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==} + engines: {node: '>=16.9.0'} hosted-git-info@8.1.0: resolution: {integrity: sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==} @@ -6165,9 +7431,6 @@ packages: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} - html-entities@2.6.0: - resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} - html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -6185,8 +7448,8 @@ packages: resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} engines: {node: '>=8'} - html-void-elements@2.0.1: - resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==} + html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} @@ -6219,10 +7482,18 @@ packages: http-deceiver@1.2.7: resolution: {integrity: sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==} + http-errors@1.4.0: + resolution: {integrity: sha512-oLjPqve1tuOl5aRhv8GK5eHpqP1C9fb+Ol+XTLjKfLltE44zdDbEdjPSbU7Ch5rSNsVFqZn97SrMmZLdu1/YMw==} + engines: {node: '>= 0.6'} + http-errors@1.6.3: resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} engines: {node: '>= 0.6'} + http-errors@1.7.3: + resolution: {integrity: sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==} + engines: {node: '>= 0.6'} + http-errors@1.8.1: resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} engines: {node: '>= 0.6'} @@ -6234,10 +7505,6 @@ packages: http-parser-js@0.5.10: resolution: {integrity: sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==} - http-proxy-agent@5.0.0: - resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} - engines: {node: '>= 6'} - http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -6265,14 +7532,14 @@ packages: resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} engines: {node: '>=10.19.0'} - https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + human-signals@1.1.1: + resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} + engines: {node: '>=8.12.0'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -6331,6 +7598,9 @@ packages: engines: {node: '>=16.x'} hasBin: true + immer@11.1.4: + resolution: {integrity: sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==} + immer@9.0.21: resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} @@ -6353,6 +7623,9 @@ packages: import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + import-sync@2.2.3: + resolution: {integrity: sha512-ZnF84+eGjetsXwYEuFZADO4eCYr1ngBo6UK476Oq4q6dkiDM1TN+6D5iQ5/e3erCyjo7O6xT3xHE6xdtCgDYhw==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -6365,10 +7638,6 @@ packages: resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} engines: {node: '>=12'} - index-to-position@1.2.0: - resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} - engines: {node: '>=18'} - infima@0.2.0-alpha.45: resolution: {integrity: sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==} engines: {node: '>=12'} @@ -6385,6 +7654,9 @@ packages: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + inherits@2.0.1: + resolution: {integrity: sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==} + inherits@2.0.3: resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} @@ -6406,9 +7678,6 @@ packages: resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==} engines: {node: ^20.17.0 || >=22.9.0} - inline-style-parser@0.1.1: - resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} - inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} @@ -6468,10 +7737,6 @@ packages: resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} engines: {node: '>= 0.4'} - is-buffer@2.0.5: - resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} - engines: {node: '>=4'} - is-builtin-module@5.0.0: resolution: {integrity: sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==} engines: {node: '>=18.20'} @@ -6556,10 +7821,6 @@ packages: resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} engines: {node: '>=10'} - is-interactive@2.0.0: - resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} - engines: {node: '>=12'} - is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -6652,10 +7913,6 @@ packages: resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} engines: {node: '>= 0.4'} - is-text-path@2.0.0: - resolution: {integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==} - engines: {node: '>=8'} - is-typed-array@1.1.15: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} @@ -6663,10 +7920,6 @@ packages: is-typedarray@1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} - is-unicode-supported@1.3.0: - resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} - engines: {node: '>=12'} - is-unicode-supported@2.1.0: resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} engines: {node: '>=18'} @@ -6683,6 +7936,10 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -6742,16 +7999,20 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jest-changed-files@30.2.0: - resolution: {integrity: sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==} + jackspeak@4.2.3: + resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} + engines: {node: 20 || >=22} + + jest-changed-files@30.3.0: + resolution: {integrity: sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-circus@30.2.0: - resolution: {integrity: sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==} + jest-circus@30.3.0: + resolution: {integrity: sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-cli@30.2.0: - resolution: {integrity: sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==} + jest-cli@30.3.0: + resolution: {integrity: sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: @@ -6760,8 +8021,8 @@ packages: node-notifier: optional: true - jest-config@30.2.0: - resolution: {integrity: sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==} + jest-config@30.3.0: + resolution: {integrity: sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: '@types/node': '*' @@ -6779,12 +8040,16 @@ packages: resolution: {integrity: sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-diff@30.3.0: + resolution: {integrity: sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-docblock@30.2.0: resolution: {integrity: sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-each@30.2.0: - resolution: {integrity: sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==} + jest-each@30.3.0: + resolution: {integrity: sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} jest-environment-jsdom@30.2.0: @@ -6796,30 +8061,42 @@ packages: canvas: optional: true - jest-environment-node@30.2.0: - resolution: {integrity: sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==} + jest-environment-node@30.3.0: + resolution: {integrity: sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-haste-map@30.2.0: - resolution: {integrity: sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==} + jest-haste-map@30.3.0: + resolution: {integrity: sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-leak-detector@30.2.0: - resolution: {integrity: sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==} + jest-leak-detector@30.3.0: + resolution: {integrity: sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} jest-matcher-utils@30.2.0: resolution: {integrity: sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-matcher-utils@30.3.0: + resolution: {integrity: sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-message-util@30.2.0: resolution: {integrity: sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-message-util@30.3.0: + resolution: {integrity: sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-mock@30.2.0: resolution: {integrity: sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-mock@30.3.0: + resolution: {integrity: sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-pnp-resolver@1.2.3: resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} engines: {node: '>=6'} @@ -6833,24 +8110,24 @@ packages: resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-resolve-dependencies@30.2.0: - resolution: {integrity: sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==} + jest-resolve-dependencies@30.3.0: + resolution: {integrity: sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-resolve@30.2.0: - resolution: {integrity: sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==} + jest-resolve@30.3.0: + resolution: {integrity: sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-runner@30.2.0: - resolution: {integrity: sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==} + jest-runner@30.3.0: + resolution: {integrity: sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-runtime@30.2.0: - resolution: {integrity: sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==} + jest-runtime@30.3.0: + resolution: {integrity: sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-snapshot@30.2.0: - resolution: {integrity: sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==} + jest-snapshot@30.3.0: + resolution: {integrity: sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} jest-util@29.7.0: @@ -6861,12 +8138,16 @@ packages: resolution: {integrity: sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-validate@30.2.0: - resolution: {integrity: sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==} + jest-util@30.3.0: + resolution: {integrity: sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-validate@30.3.0: + resolution: {integrity: sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-watcher@30.2.0: - resolution: {integrity: sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==} + jest-watcher@30.3.0: + resolution: {integrity: sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} jest-worker@27.5.1: @@ -6877,12 +8158,12 @@ packages: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - jest-worker@30.2.0: - resolution: {integrity: sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==} + jest-worker@30.3.0: + resolution: {integrity: sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest@30.2.0: - resolution: {integrity: sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==} + jest@30.3.0: + resolution: {integrity: sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: @@ -6950,9 +8231,6 @@ packages: engines: {node: '>=6'} hasBin: true - json-bigint@1.0.0: - resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} - json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -6977,12 +8255,22 @@ packages: resolution: {integrity: sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w==} engines: {node: '>=12.0.0'} + json-schema-to-ts@1.6.4: + resolution: {integrity: sha512-pR4yQ9DHz6itqswtHCm26mw45FSNfQ9rEQjosaZErhn5J3J2sIViQiz8rDaezjKAhFGpmsoczYVBgGHzFw/stA==} + + json-schema-to-ts@2.7.2: + resolution: {integrity: sha512-R1JfqKqbBR4qE8UyBR56Ms30LL62/nlhoz+1UkfI/VE7p54Awu919FZ6ZUPG8zIa3XB65usPJgr1ONVncUGSaQ==} + engines: {node: '>=16'} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} @@ -7005,8 +8293,8 @@ packages: jsonc-parser@2.2.1: resolution: {integrity: sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==} - jsonc-parser@3.3.1: - resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} @@ -7014,10 +8302,6 @@ packages: jsonify@0.0.1: resolution: {integrity: sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==} - jsonparse@1.3.1: - resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} - engines: {'0': node >= 0.2.0} - jsonpath-plus@10.3.0: resolution: {integrity: sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==} engines: {node: '>=18.0.0'} @@ -7027,6 +8311,10 @@ packages: resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} engines: {node: '>=0.10.0'} + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} + engines: {node: '>=12', npm: '>=6'} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -7040,7 +8328,6 @@ packages: keygrip@1.1.0: resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} engines: {node: '>= 0.6'} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -7053,13 +8340,17 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - koa-compose@4.1.0: resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} + koa-send@5.0.1: + resolution: {integrity: sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==} + engines: {node: '>= 8'} + + koa-static@5.0.0: + resolution: {integrity: sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==} + engines: {node: '>= 7.6.0'} + koa@3.1.1: resolution: {integrity: sha512-KDDuvpfqSK0ZKEO2gCPedNjl5wYpfj+HNiuVRlbhd1A88S3M0ySkdf2V/EJ4NWt5dwh5PXCdcenrKK2IQJAxsg==} engines: {node: '>= 18'} @@ -7067,28 +8358,102 @@ packages: language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} - language-tags@1.0.9: - resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} - engines: {node: '>=0.10'} + language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} + + latest-version@7.0.0: + resolution: {integrity: sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==} + engines: {node: '>=14.16'} + + launch-editor@2.12.0: + resolution: {integrity: sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==} + + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] - latest-version@7.0.0: - resolution: {integrity: sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==} - engines: {node: '>=14.16'} + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] - launch-editor@2.12.0: - resolution: {integrity: sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==} + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] - lazystream@1.0.1: - resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} - engines: {node: '>= 0.6.3'} + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] - leven@3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} - engines: {node: '>=6'} + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} @@ -7097,8 +8462,8 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - lint-staged@16.2.7: - resolution: {integrity: sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==} + lint-staged@16.4.0: + resolution: {integrity: sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==} engines: {node: '>=20.17'} hasBin: true @@ -7144,9 +8509,24 @@ packages: lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.kebabcase@4.1.1: resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} @@ -7159,6 +8539,9 @@ packages: lodash.mergewith@4.6.2: resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.snakecase@4.1.1: resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} @@ -7177,9 +8560,8 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - log-symbols@6.0.0: - resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} - engines: {node: '>=18'} + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} log-update@6.1.0: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} @@ -7212,6 +8594,10 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -7258,60 +8644,33 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - mdast-util-definitions@5.1.2: - resolution: {integrity: sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==} - mdast-util-directive@3.1.0: resolution: {integrity: sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==} - mdast-util-find-and-replace@2.2.2: - resolution: {integrity: sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==} - mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} - mdast-util-from-markdown@1.3.1: - resolution: {integrity: sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==} - mdast-util-from-markdown@2.0.2: resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} mdast-util-frontmatter@2.0.1: resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==} - mdast-util-gfm-autolink-literal@1.0.3: - resolution: {integrity: sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==} - mdast-util-gfm-autolink-literal@2.0.1: resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} - mdast-util-gfm-footnote@1.0.2: - resolution: {integrity: sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==} - mdast-util-gfm-footnote@2.1.0: resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} - mdast-util-gfm-strikethrough@1.0.3: - resolution: {integrity: sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==} - mdast-util-gfm-strikethrough@2.0.0: resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} - mdast-util-gfm-table@1.0.7: - resolution: {integrity: sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==} - mdast-util-gfm-table@2.0.0: resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} - mdast-util-gfm-task-list-item@1.0.2: - resolution: {integrity: sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==} - mdast-util-gfm-task-list-item@2.0.0: resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} - mdast-util-gfm@2.0.2: - resolution: {integrity: sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==} - mdast-util-gfm@3.1.0: resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} @@ -7327,27 +8686,15 @@ packages: mdast-util-mdxjs-esm@2.0.1: resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} - mdast-util-phrasing@3.0.1: - resolution: {integrity: sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==} - mdast-util-phrasing@4.1.0: resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} - mdast-util-to-hast@12.3.0: - resolution: {integrity: sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==} - mdast-util-to-hast@13.2.1: resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} - mdast-util-to-markdown@1.5.0: - resolution: {integrity: sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==} - mdast-util-to-markdown@2.1.2: resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} - mdast-util-to-string@3.2.0: - resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==} - mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} @@ -7368,10 +8715,6 @@ packages: memfs@4.51.1: resolution: {integrity: sha512-Eyt3XrufitN2ZL9c/uIRMyDwXanLI88h/L3MoWqNY747ha3dMR9dWqp8cRT5ntjZ0U1TNuq4U91ZXK0sMBjYOQ==} - meow@12.1.1: - resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} - engines: {node: '>=16.10'} - meow@13.2.0: resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} engines: {node: '>=18'} @@ -7394,8 +8737,10 @@ packages: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} - micromark-core-commonmark@1.1.0: - resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==} + micro@9.3.5-canary.3: + resolution: {integrity: sha512-viYIo9PefV+w9dvoIBh1gI44Mvx1BOk67B4BpC2QK77qdY0xZF0Q+vWLt/BII6cLkIc8rLmSIcJaB/OrXXKe1g==} + engines: {node: '>= 8.0.0'} + hasBin: true micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -7406,45 +8751,24 @@ packages: micromark-extension-frontmatter@2.0.0: resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==} - micromark-extension-gfm-autolink-literal@1.0.5: - resolution: {integrity: sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==} - micromark-extension-gfm-autolink-literal@2.1.0: resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} - micromark-extension-gfm-footnote@1.1.2: - resolution: {integrity: sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==} - micromark-extension-gfm-footnote@2.1.0: resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} - micromark-extension-gfm-strikethrough@1.0.7: - resolution: {integrity: sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==} - micromark-extension-gfm-strikethrough@2.1.0: resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} - micromark-extension-gfm-table@1.0.7: - resolution: {integrity: sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==} - micromark-extension-gfm-table@2.1.1: resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} - micromark-extension-gfm-tagfilter@1.0.2: - resolution: {integrity: sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==} - micromark-extension-gfm-tagfilter@2.0.0: resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} - micromark-extension-gfm-task-list-item@1.0.5: - resolution: {integrity: sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==} - micromark-extension-gfm-task-list-item@2.1.0: resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} - micromark-extension-gfm@2.0.3: - resolution: {integrity: sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==} - micromark-extension-gfm@3.0.0: resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} @@ -7463,15 +8787,9 @@ packages: micromark-extension-mdxjs@3.0.0: resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} - micromark-factory-destination@1.1.0: - resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==} - micromark-factory-destination@2.0.1: resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} - micromark-factory-label@1.1.0: - resolution: {integrity: sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==} - micromark-factory-label@2.0.1: resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} @@ -7484,15 +8802,9 @@ packages: micromark-factory-space@2.0.1: resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} - micromark-factory-title@1.1.0: - resolution: {integrity: sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==} - micromark-factory-title@2.0.1: resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} - micromark-factory-whitespace@1.1.0: - resolution: {integrity: sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==} - micromark-factory-whitespace@2.0.1: resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} @@ -7502,72 +8814,39 @@ packages: micromark-util-character@2.1.1: resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} - micromark-util-chunked@1.1.0: - resolution: {integrity: sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==} - micromark-util-chunked@2.0.1: resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} - micromark-util-classify-character@1.1.0: - resolution: {integrity: sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==} - micromark-util-classify-character@2.0.1: resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} - micromark-util-combine-extensions@1.1.0: - resolution: {integrity: sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==} - micromark-util-combine-extensions@2.0.1: resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} - micromark-util-decode-numeric-character-reference@1.1.0: - resolution: {integrity: sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==} - micromark-util-decode-numeric-character-reference@2.0.2: resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} - micromark-util-decode-string@1.1.0: - resolution: {integrity: sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==} - micromark-util-decode-string@2.0.1: resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} - micromark-util-encode@1.1.0: - resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==} - micromark-util-encode@2.0.1: resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} micromark-util-events-to-acorn@2.0.3: resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} - micromark-util-html-tag-name@1.2.0: - resolution: {integrity: sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==} - micromark-util-html-tag-name@2.0.1: resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} - micromark-util-normalize-identifier@1.1.0: - resolution: {integrity: sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==} - micromark-util-normalize-identifier@2.0.1: resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} - micromark-util-resolve-all@1.1.0: - resolution: {integrity: sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==} - micromark-util-resolve-all@2.0.1: resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} - micromark-util-sanitize-uri@1.2.0: - resolution: {integrity: sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==} - micromark-util-sanitize-uri@2.0.1: resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} - micromark-util-subtokenize@1.1.0: - resolution: {integrity: sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==} - micromark-util-subtokenize@2.1.0: resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} @@ -7583,9 +8862,6 @@ packages: micromark-util-types@2.0.2: resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} - micromark@3.2.0: - resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} - micromark@4.0.2: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} @@ -7605,8 +8881,8 @@ packages: resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} engines: {node: '>= 0.6'} - mime-format@2.0.1: - resolution: {integrity: sha512-XxU3ngPbEnrYnNbIX+lYSaYg0M01v6p2ntd2YaFksTu0vayaw5OJvbdRyWs07EYRlLED5qadUZ+xo+XhOvFhwg==} + mime-format@2.0.2: + resolution: {integrity: sha512-Y5ERWVcyh3sby9Fx2U5F1yatiTFjNsqF5NltihTWI9QgNtr5o3dbCZdcKa1l2wyfhnwwoP9HGNxga7LqZLA6gw==} mime-types@2.1.18: resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==} @@ -7668,9 +8944,16 @@ packages: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minimatch@5.1.6: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} @@ -7702,6 +8985,9 @@ packages: resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} engines: {node: '>=8'} + minipass@2.9.0: + resolution: {integrity: sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==} + minipass@3.3.6: resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} engines: {node: '>=8'} @@ -7710,6 +8996,9 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minizlib@1.3.3: + resolution: {integrity: sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==} + minizlib@3.1.0: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} @@ -7726,6 +9015,11 @@ packages: engines: {node: '>=10'} hasBin: true + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} @@ -7746,6 +9040,12 @@ packages: ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + ms@2.1.1: + resolution: {integrity: sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -7761,9 +9061,9 @@ packages: resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} hasBin: true - mute-stream@2.0.0: - resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} - engines: {node: ^18.17.0 || >=20.5.0} + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -7771,15 +9071,16 @@ packages: nan@2.24.0: resolution: {integrity: sha512-Vpf9qnVW1RaDkoNKFUvfxqAbtI8ncb8OJlqZ9wwpXzWPEsvsB1nvdUi6oYrHIkQ1Y/tMDnr1h4nczS0VB9Xykg==} - nano-spawn@2.0.0: - resolution: {integrity: sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==} - engines: {node: '>=20.17'} - nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@5.1.7: + resolution: {integrity: sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ==} + engines: {node: ^18 || >=20} + hasBin: true + napi-postinstall@0.3.4: resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -7829,6 +9130,24 @@ packages: resolution: {integrity: sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==} engines: {node: 4.x || >=6.0.0} + node-fetch@2.6.7: + resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-fetch@2.6.9: + resolution: {integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -7842,6 +9161,10 @@ packages: resolution: {integrity: sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==} engines: {node: '>= 6.13.0'} + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + node-gyp@12.1.0: resolution: {integrity: sha512-W+RYA8jBnhSr2vrTtlPYPc1K+CSjGpVDRZxcqJcERZ8ND3A1ThWPHRwctTx3qC3oW99jt726jhdz3Y6ky87J4g==} engines: {node: ^20.17.0 || >=22.9.0} @@ -7860,15 +9183,16 @@ packages: resolution: {integrity: sha512-Pzr3rol8fvhG/oJjIq2NTVB0vmdNNlz22FENhhPojYRZ4/ee08CfK4YuKmuL54V9MLhI1kpzxfOJ/63LzmZzDg==} engines: {node: '>=14'} + nopt@8.1.0: + resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + nopt@9.0.0: resolution: {integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==} engines: {node: ^20.17.0 || >=22.9.0} hasBin: true - normalize-package-data@6.0.2: - resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} - engines: {node: ^16.14.0 || >=18.0.0} - normalize-package-data@7.0.1: resolution: {integrity: sha512-linxNAT6M0ebEYZOx2tO6vBEFsVgnPpv+AVjk0wJHfaUIbq31Jm3T6vvZaarnOeWDh8ShnwXuaAyM7WT3RzErA==} engines: {node: ^18.17.0 || >=20.5.0} @@ -7889,10 +9213,6 @@ packages: resolution: {integrity: sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==} engines: {node: ^20.17.0 || >=22.9.0} - npm-package-arg@12.0.2: - resolution: {integrity: sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==} - engines: {node: ^18.17.0 || >=20.5.0} - npm-package-arg@13.0.2: resolution: {integrity: sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA==} engines: {node: ^20.17.0 || >=22.9.0} @@ -7909,6 +9229,11 @@ packages: resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} engines: {node: '>=18'} + npmlog@7.0.1: + resolution: {integrity: sha512-uJ0YFk/mCQpLBt+bxN88AKd+gyqZvZDbtiNxk6Waqcj2aPRyfVx8ITawkyQynxUagInjdYT1+qj4NfA5KJJUxg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + deprecated: This package is no longer supported. + nprogress@0.2.0: resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} @@ -7956,6 +9281,10 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + object-is@1.1.6: + resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} + engines: {node: '>= 0.4'} + object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -7994,6 +9323,9 @@ packages: resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} engines: {node: '>= 0.8'} + once@1.3.3: + resolution: {integrity: sha512-6vaNInhu+CHxtONf3zw3vq4SP2DOQhjBvIa3rNcG0+P7eKWlYH6Peu7rHizSloRU2EwMz6GraLieis9Ac9+p1w==} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -8013,11 +9345,14 @@ packages: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} - openapi-to-postmanv2@4.25.0: - resolution: {integrity: sha512-sIymbkQby0gzxt2Yez8YKB6hoISEel05XwGwNrAhr6+vxJWXNxkmssQc/8UEtVkuJ9ZfUXLkip9PYACIpfPDWg==} - engines: {node: '>=8'} + openapi-to-postmanv2@6.0.0: + resolution: {integrity: sha512-PU676AZh/7A1CFi7MOsIRTnasEauK1EQL1c4ktAOZIknoPh7OZ3yC1NoRHfogoTfS/9WFXV+uOP1mg+402fuoQ==} + engines: {node: '>=18'} hasBin: true + openapi-types@12.1.3: + resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + opener@1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true @@ -8026,9 +9361,9 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - ora@8.2.0: - resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} - engines: {node: '>=18'} + os-paths@4.4.0: + resolution: {integrity: sha512-wrAwOeXp1RRMFfQY8Sy7VaGVmPocaLwSFOYCGKSyo8qmJ+/yaafCl5BCA1IQZWqFSRBrKDYFeR9d/VyQzfH/jg==} + engines: {node: '>= 6.0'} own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} @@ -8042,6 +9377,10 @@ packages: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} + p-finally@2.0.1: + resolution: {integrity: sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==} + engines: {node: '>=8'} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -8054,8 +9393,8 @@ packages: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - p-limit@7.2.0: - resolution: {integrity: sha512-ATHLtwoTNDloHRFFxFJdHnG6n2WUeFjaR8XQMFdKIv0xkXjrER8/iG9iu265jOM95zXHAfv9oTkqhrfbIzosrQ==} + p-limit@7.3.0: + resolution: {integrity: sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==} engines: {node: '>=20'} p-locate@4.1.0: @@ -8078,22 +9417,14 @@ packages: resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} engines: {node: '>=18'} - p-pipe@4.0.0: - resolution: {integrity: sha512-HkPfFklpZQPUKBFXzKFB6ihLriIHxnmuQdK9WmLDwe4hf2PdhhfWT/FJa+pc3bA1ywvKXtedxIRmd4Y7BTXE4w==} - engines: {node: '>=12'} - p-queue@6.6.2: resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} engines: {node: '>=8'} - p-queue@9.0.1: - resolution: {integrity: sha512-RhBdVhSwJb7Ocn3e8ULk4NMwBEuOxe+1zcgphUy9c2e5aR/xbEsdVXxHJ3lynw6Qiqu7OINEyHlZkiblEpaq7w==} + p-queue@9.1.2: + resolution: {integrity: sha512-ktsDOALzTYTWWF1PbkNVg2rOt+HaOaMWJMUnt7T3qf5tvZ1L8dBW3tObzprBcXNMKkwj+yFSLqHso0x+UFcJXw==} engines: {node: '>=20'} - p-reduce@3.0.0: - resolution: {integrity: sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==} - engines: {node: '>=12'} - p-retry@6.2.1: resolution: {integrity: sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==} engines: {node: '>=16.17'} @@ -8138,9 +9469,9 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} - parse-json@8.3.0: - resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} - engines: {node: '>=18'} + parse-ms@2.1.0: + resolution: {integrity: sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==} + engines: {node: '>=6'} parse-ms@4.0.0: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} @@ -8149,6 +9480,10 @@ packages: parse-numeric-range@1.3.0: resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==} + parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + parse-path@7.1.0: resolution: {integrity: sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw==} @@ -8159,9 +9494,6 @@ packages: parse5-htmlparser2-tree-adapter@7.1.0: resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} - parse5@6.0.1: - resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} - parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} @@ -8183,6 +9515,10 @@ packages: resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + path-expression-matcher@1.5.0: + resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} + engines: {node: '>=14.0.0'} + path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -8198,6 +9534,9 @@ packages: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} + path-match@1.2.4: + resolution: {integrity: sha512-UWlehEdqu36jmh4h5CWJ7tARp1OEVKGHKm6+dg9qMq5RKUTV5WJrGgaZ3dN2m7WFAXDbjlHzvJvL/IUpy84Ktw==} + path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -8218,6 +9557,15 @@ packages: path-to-regexp@3.3.0: resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} + path-to-regexp@6.1.0: + resolution: {integrity: sha512-h9DqehX3zZZDCEm+xbfU0ZmwCGFCAAraPJWMXJ4+v32NjZJilVg3k1TcKsRgIb8IQ/izZSaydDc1OhJCZvs2Dw==} + + path-to-regexp@6.2.1: + resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} @@ -8225,40 +9573,36 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - path-type@6.0.0: - resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==} - engines: {node: '>=18'} - - path@0.12.7: - resolution: {integrity: sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pg-cloudflare@1.2.7: - resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==} + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + pg-cloudflare@1.3.0: + resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} - pg-connection-string@2.9.1: - resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==} + pg-connection-string@2.12.0: + resolution: {integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==} pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} - pg-pool@3.10.1: - resolution: {integrity: sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==} + pg-pool@3.13.0: + resolution: {integrity: sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==} peerDependencies: pg: '>=8.0' - pg-protocol@1.10.3: - resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==} + pg-protocol@1.13.0: + resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==} pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} - pg@8.16.3: - resolution: {integrity: sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==} + pg@8.20.0: + resolution: {integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==} engines: {node: '>= 16.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -8273,6 +9617,9 @@ packages: resolution: {integrity: sha512-nKaQY9wtuiidwLMdVIce1O3kL0d+FxrigCVzsShnoqzOSaWWWOvuctb/sYwlai5cTwwzRSNa+a/NtN2kVZGNJw==} engines: {node: '>= 18'} + picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -8284,14 +9631,9 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} - pidtree@0.6.0: - resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} - engines: {node: '>=0.10'} - hasBin: true - - pify@6.1.0: - resolution: {integrity: sha512-KocF8ve28eFjjuBKKGvzOBGzG8ew2OqOOSxTTZhirkzH7h3BI1vyzqlR0qbfcDBve1Yzo3FVlWUAtCRrbVN8Fw==} - engines: {node: '>=14.16'} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} @@ -8749,32 +10091,32 @@ packages: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} - postman-code-generators@1.14.2: - resolution: {integrity: sha512-qZAyyowfQAFE4MSCu2KtMGGQE/+oG1JhMZMJNMdZHYCSfQiVVeKxgk3oI4+KJ3d1y5rrm2D6C6x+Z+7iyqm+fA==} - engines: {node: '>=12'} + postman-code-generators@2.1.1: + resolution: {integrity: sha512-+egQK1Jf9a92QP23vRTKcDLOthIQmI7WI4czEsZq/wgguLMnVHJ26KlT8AVtpAdVw28hqUbHwicerYxRWCfjoA==} + engines: {node: '>=18'} - postman-collection@4.5.0: - resolution: {integrity: sha512-152JSW9pdbaoJihwjc7Q8lc3nPg/PC9lPTHdMk7SHnHhu/GBJB7b2yb9zG7Qua578+3PxkQ/HYBuXpDSvsf7GQ==} - engines: {node: '>=10'} + postman-collection@5.3.0: + resolution: {integrity: sha512-PMa5vRheqDFfS1bkRg8WBidWxunRA80sT5YNLP27YC5+ycyfiLMCwPnqQd1zfvxkGk04Pr9UronWmmgsbpsVyQ==} + engines: {node: '>=18'} - postman-url-encoder@3.0.5: - resolution: {integrity: sha512-jOrdVvzUXBC7C+9gkIkpDJ3HIxOHTIqjpQ4C1EMt1ZGeMvSEpbFCKq23DEfgsj46vMnDgyQf+1ZLp2Wm+bKSsA==} + postman-url-encoder@3.0.8: + resolution: {integrity: sha512-EOgUMBazo7JNP4TDrd64TsooCiWzzo4143Ws8E8WYGEpn2PKpq+S4XRTDhuRTYHm3VKOpUZs7ZYZq7zSDuesqA==} engines: {node: '>=10'} prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier-linter-helpers@1.0.0: - resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + prettier-linter-helpers@1.0.1: + resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} engines: {node: '>=6.0.0'} prettier-package-json@2.8.0: resolution: {integrity: sha512-WxtodH/wWavfw3MR7yK/GrS4pASEQ+iSTkdtSxPJWvqzG55ir5nvbLt9rw5AOiEcqqPCRM92WCtR1rk3TG3JSQ==} hasBin: true - prettier@3.7.4: - resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} engines: {node: '>=14'} hasBin: true @@ -8789,6 +10131,14 @@ packages: resolution: {integrity: sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + pretty-format@30.3.0: + resolution: {integrity: sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + pretty-ms@7.0.1: + resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==} + engines: {node: '>=10'} + pretty-ms@9.3.0: resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} engines: {node: '>=18'} @@ -8809,10 +10159,6 @@ packages: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} - proc-log@5.0.0: - resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==} - engines: {node: ^18.17.0 || >=20.5.0} - proc-log@6.1.0: resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==} engines: {node: ^20.17.0 || >=22.9.0} @@ -8831,6 +10177,9 @@ packages: promise@7.3.1: resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} + promisepipe@3.0.0: + resolution: {integrity: sha512-V6TbZDJ/ZswevgkDNpGt/YqNCiZP9ASfgU+p83uJE6NrGtvSGoOcHLiDCqkMs2+yg7F5qHdLV8d0aS8O26G/KA==} + prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -8841,12 +10190,9 @@ packages: proper-lockfile@4.1.2: resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} - properties-reader@2.3.0: - resolution: {integrity: sha512-z597WicA7nDZxK12kZqHr2TcvwNU1GCfA5UwfDY/HDp3hXPoPlb5rlEx9bwGTiJnc0OqbBTkU975jDToth8Gxw==} - engines: {node: '>=14'} - - property-information@6.5.0: - resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + properties-reader@3.0.1: + resolution: {integrity: sha512-WPn+h9RGEExOKdu4bsF4HksG/uzd3cFq3MFtq8PsFeExPse5Ha/VOjQNyHhjboBFwGXGev6muJYTSPAOkROq2g==} + engines: {node: '>=18'} property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} @@ -8865,6 +10211,10 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} @@ -8882,16 +10232,9 @@ packages: resolution: {integrity: sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==} engines: {node: '>=12.20'} - pure-rand@6.1.0: - resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - pure-rand@7.0.1: resolution: {integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==} - qs@6.14.0: - resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} - engines: {node: '>=0.6'} - qs@6.14.1: resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} engines: {node: '>=0.6'} @@ -8919,6 +10262,10 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} + raw-body@2.4.1: + resolution: {integrity: sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==} + engines: {node: '>= 0.8'} + raw-body@2.5.3: resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} engines: {node: '>= 0.8'} @@ -8931,10 +10278,10 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - react-dom@19.2.3: - resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} + react-dom@19.2.5: + resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} peerDependencies: - react: ^19.2.3 + react: ^19.2.5 react-fast-compare@3.2.2: resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} @@ -8970,8 +10317,8 @@ packages: react: '>=18.0.0' react-dom: '>=18.0.0' - react-loadable-ssr-addon-v5-slorber@1.0.1: - resolution: {integrity: sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==} + react-loadable-ssr-addon-v5-slorber@1.0.3: + resolution: {integrity: sha512-GXfh9VLwB5ERaCsU6RULh7tkemeX15aNh6wuMEBtfdyMa7fFG8TXrhXlx1SoEK2Ty/l6XIkzzYIQmyaWW3JgdQ==} engines: {node: '>=10.13.0'} peerDependencies: react-loadable: '*' @@ -8980,11 +10327,11 @@ packages: react-magic-dropzone@1.0.1: resolution: {integrity: sha512-0BIROPARmXHpk4AS3eWBOsewxoM5ndk2psYP/JmbCq8tz3uR2LIV1XiroZ9PKrmDRMctpW+TvsBCtWasuS8vFA==} - react-markdown@8.0.7: - resolution: {integrity: sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==} + react-markdown@10.1.0: + resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} peerDependencies: - '@types/react': '>=16' - react: '>=16' + '@types/react': '>=18' + react: '>=18' react-modal@3.16.3: resolution: {integrity: sha512-yCYRJB5YkeQDQlTt17WGAgFJ7jr2QYcWa1SHqZ3PluDmnKJ/7+tVU+E6uKyZ0nODaeEj+xCpK4LcSnKXLMC0Nw==} @@ -8992,16 +10339,16 @@ packages: react: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19 react-dom: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19 - react-redux@7.2.9: - resolution: {integrity: sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==} + react-redux@9.2.0: + resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} peerDependencies: - react: ^16.8.3 || ^17 || ^18 - react-dom: '*' - react-native: '*' + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 peerDependenciesMeta: - react-dom: + '@types/react': optional: true - react-native: + redux: optional: true react-router-config@5.1.1: @@ -9020,18 +10367,10 @@ packages: peerDependencies: react: '>=15' - react@19.2.3: - resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} + react@19.2.5: + resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} engines: {node: '>=0.10.0'} - read-pkg@9.0.1: - resolution: {integrity: sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==} - engines: {node: '>=18'} - - read-yaml-file@2.1.0: - resolution: {integrity: sha512-UkRNRIwnhG+y7hpqnycCL/xbTk7+ia9VuVTC0S+zVbwd65DI9eUpRMfsWIGrCWxTU/mi+JW8cHQCrv+zfCbEPQ==} - engines: {node: '>=10.13'} - readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -9076,13 +10415,13 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} - redux-thunk@2.4.2: - resolution: {integrity: sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==} + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} peerDependencies: - redux: ^4 + redux: ^5.0.0 - redux@4.2.1: - resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} @@ -9128,9 +10467,6 @@ packages: resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} hasBin: true - rehype-raw@6.1.1: - resolution: {integrity: sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==} - rehype-raw@7.0.0: resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} @@ -9157,24 +10493,15 @@ packages: remark-frontmatter@5.0.0: resolution: {integrity: sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==} - remark-gfm@3.0.1: - resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==} - remark-gfm@4.0.1: resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} remark-mdx@3.1.1: resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==} - remark-parse@10.0.2: - resolution: {integrity: sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==} - remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} - remark-rehype@10.1.0: - resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==} - remark-rehype@11.1.2: resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} @@ -9206,8 +10533,8 @@ packages: requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - reselect@4.1.8: - resolution: {integrity: sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==} + reselect@5.1.1: + resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} reserved@0.1.2: resolution: {integrity: sha512-/qO54MWj5L8WCBP9/UNe2iefJc+L9yETbH32xO/ft/EYPOTCR5k+azvDUgdCOKwZH8hXwPd0b8XBL78Nn2U69g==} @@ -9223,6 +10550,10 @@ packages: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} + resolve-dir@1.0.1: + resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -9231,6 +10562,10 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve-path@1.4.0: + resolution: {integrity: sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==} + engines: {node: '>= 0.8'} + resolve-pathname@3.0.0: resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==} @@ -9257,10 +10592,6 @@ packages: retry-as-promised@7.1.1: resolution: {integrity: sha512-hMD7odLOt3LkTjcif8aRZqi/hybjpLNgSk5oF5FCowfCjok6LukpN2bDX7R5wDmbgBQFn7YoBxSagmtXHaJYJw==} - retry-request@7.0.2: - resolution: {integrity: sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==} - engines: {node: '>=14'} - retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} @@ -9305,10 +10636,6 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - sade@1.8.1: - resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} - engines: {node: '>=6'} - safe-array-concat@1.1.3: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} @@ -9405,8 +10732,13 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} hasBin: true @@ -9415,6 +10747,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + send@0.19.2: resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} engines: {node: '>= 0.8.0'} @@ -9476,8 +10813,8 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - serve-handler@6.1.6: - resolution: {integrity: sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==} + serve-handler@6.1.7: + resolution: {integrity: sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==} serve-index@1.9.1: resolution: {integrity: sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==} @@ -9512,6 +10849,9 @@ packages: setprototypeof@1.1.0: resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} + setprototypeof@1.1.1: + resolution: {integrity: sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -9576,6 +10916,10 @@ packages: signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + signal-exit@4.0.2: + resolution: {integrity: sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==} + engines: {node: '>=14'} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -9584,6 +10928,9 @@ packages: resolution: {integrity: sha512-LH7FpTAkeD+y5xQC4fzS+tFtaNlvt3Ib1zKzvhjv/Y+cioV4zIuw4IZr2yhRLu67CWL7FR9/6KXKnjRoZTvGGQ==} engines: {node: '>=12'} + simple-git@3.36.0: + resolution: {integrity: sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==} + sirv@2.0.4: resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} engines: {node: '>= 10'} @@ -9608,10 +10955,6 @@ packages: resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} engines: {node: '>=12'} - slash@5.1.0: - resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} - engines: {node: '>=14.16'} - slice-ansi@7.1.2: resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} engines: {node: '>=18'} @@ -9642,10 +10985,6 @@ packages: resolution: {integrity: sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==} engines: {node: '>= 6.3.0'} - sort-keys@5.1.0: - resolution: {integrity: sha512-aSbHV0DaBcr7u0PVHXzM6NbZNAtrr9sF6+Qfs9UUVG7Ll3jQ6hHi8F/xqIIcn2rvIVbr0v/2zyjSdwSV47AgLQ==} - engines: {node: '>=12'} - sort-keys@6.0.0: resolution: {integrity: sha512-ueSlHJMwpIw42CJ4B11Uxzh/S0p0AlOyiNktlv2KOu5e1JpUE6DlC4AAUjXqesHdBRv/g0wC9Q4vwq0NP2pA9w==} engines: {node: '>=20'} @@ -9740,6 +11079,9 @@ packages: stacktracey@2.1.8: resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==} + stat-mode@0.3.0: + resolution: {integrity: sha512-QjMLR0A3WwFY2aZdV0okfFEJB5TRjkggXZjxP3A1RsWsNHNu3YPv8btmtc6iCFZ0Rul3FE93OYogvhOUClU+ng==} + statuses@1.5.0: resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} engines: {node: '>= 0.6'} @@ -9751,19 +11093,18 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} - stdin-discarder@0.2.2: - resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} - engines: {node: '>=18'} - stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} - stream-events@1.0.5: - resolution: {integrity: sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==} + stream-browserify@3.0.0: + resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} - stream-shift@1.0.3: - resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} + stream-to-array@2.3.0: + resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==} + + stream-to-promise@2.2.0: + resolution: {integrity: sha512-HAGUASw8NT0k8JvIVutB2Y/9iBk7gpgEyAudXwNJmZERdMITGdajOa4VJfD/kNiA3TppQpTP4J+CtcHwdzKBAw==} streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} @@ -9876,18 +11217,12 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - strnum@1.1.2: - resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==} - - stubs@3.0.0: - resolution: {integrity: sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==} + strnum@2.2.3: + resolution: {integrity: sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==} style-to-js@1.1.21: resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} - style-to-object@0.4.4: - resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} - style-to-object@1.0.14: resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} @@ -9937,6 +11272,12 @@ packages: resolution: {integrity: sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==} hasBin: true + swc-loader@0.2.7: + resolution: {integrity: sha512-nwYWw3Fh9ame3Rtm7StS9SBLpHRRnYcK7bnpF3UKZmesAK0gw2/ADvlURFAINmPvKtDLzp+GBiP9yLoEjg6S9w==} + peerDependencies: + '@swc/core': ^1.2.147 + webpack: '>=2' + swr@2.3.8: resolution: {integrity: sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w==} peerDependencies: @@ -9949,9 +11290,53 @@ packages: resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} engines: {node: ^14.18.0 || >=16.0.0} - syncpack@13.0.4: - resolution: {integrity: sha512-kJ9VlRxNCsBD5pJAE29oXeBYbPLhEySQmK4HdpsLv81I6fcDDW17xeJqMwiU3H7/woAVsbgq25DJNS8BeiN5+w==} - engines: {node: '>=18.18.0'} + synckit@0.11.12: + resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} + engines: {node: ^14.18.0 || >=16.0.0} + + syncpack-darwin-arm64@14.3.0: + resolution: {integrity: sha512-gpbkBzO7yqa3BONc4EU3jY07yiPSZdoAxcpnz8REV9Bc6FkmKfOejCpYIh8RaogGPS4gOLJ/RUJEECqAaHTcjA==} + cpu: [arm64] + os: [darwin] + + syncpack-darwin-x64@14.3.0: + resolution: {integrity: sha512-wTpl6Qj5qGIHrYhpCrlNnosmhQqvUoidqqmxtdM3f+j+b+OkTtpkUl2tdE28h3aeEEUPf9ClQnHuwRJMYNlrJw==} + cpu: [x64] + os: [darwin] + + syncpack-linux-arm64-musl@14.3.0: + resolution: {integrity: sha512-AezJ5dv0s+l/p1l4/wBatYhM6SZEKLcyNKggSOX5uISzqbSKwj/Aak13pBXWarzS+N6LnOl4PMcwRMJPOUfN/g==} + cpu: [arm64] + os: [linux] + + syncpack-linux-arm64@14.3.0: + resolution: {integrity: sha512-Vcf9zWkJGRqb5mGPKi9E+s/mB/Tw08LmKGRaiyKJjK8bhd1Ds65O8A2lOidy3jg0NOOojqREmDsli74Xd0z6KA==} + cpu: [arm64] + os: [linux] + + syncpack-linux-x64-musl@14.3.0: + resolution: {integrity: sha512-tIRF0lvBJcoIwcO05/Q6j30CAg0jzn+A5eQL+06Ncq8CE5i8fBWrVwN1U/QQ4fzT+tondNWH/2BR5zlaB1VUpQ==} + cpu: [x64] + os: [linux] + + syncpack-linux-x64@14.3.0: + resolution: {integrity: sha512-n/4iBJnoOCe5An+WYlaqfSxOKQ7Id3TZTpxOpI60Cucq3yqwq0JHQUielj6JBtVaxvo2rAsTwbCLyp2aB0SD2g==} + cpu: [x64] + os: [linux] + + syncpack-windows-arm64@14.3.0: + resolution: {integrity: sha512-ZTX0rSUTJZjIde3qPKLEqU7IEt4KxFsmq6gKLfESx5rB1rxk/B1Ljv6nTYph5QVItEzHQHUXDWnqa9yCb15myw==} + cpu: [arm64] + os: [win32] + + syncpack-windows-x64@14.3.0: + resolution: {integrity: sha512-zvbphZw40wFZYeEW2oJYqA9Uw6IOwNIpgFmVNdWdGmShPF/Zu1cavHEcGwtQmvVzwuTEyiu67uxQ3hDC2q6vtg==} + cpu: [x64] + os: [win32] + + syncpack@14.3.0: + resolution: {integrity: sha512-8/WtPPxxGFqE21JPFz6Bpw0m9BT3lzMYDcewJFj++EBPmCaDWowTnx0R4V7ofH/1z3HAIaAps6g2GHL76l1Y2g==} + engines: {node: '>=14.17.0'} hasBin: true tapable@2.3.0: @@ -9961,8 +11346,8 @@ packages: tar-fs@2.1.4: resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} - tar-fs@3.1.1: - resolution: {integrity: sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==} + tar-fs@3.1.2: + resolution: {integrity: sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==} tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} @@ -9971,13 +11356,15 @@ packages: tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + tar@4.4.18: + resolution: {integrity: sha512-ZuOtqqmkV9RE1+4odd+MhBpibmCxNP6PJhH/h2OqNuotTX7/XHPZQJv2pKvWMplFH9SIZZhitehh6vBH6LO8Pg==} + engines: {node: '>=4.5'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + tar@7.5.2: resolution: {integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==} engines: {node: '>=18'} - - teeny-request@9.0.0: - resolution: {integrity: sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==} - engines: {node: '>=14'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me terser-webpack-plugin@5.3.16: resolution: {integrity: sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==} @@ -10004,16 +11391,12 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} - testcontainers@11.11.0: - resolution: {integrity: sha512-nKTJn3n/gkyGg/3SVkOwX+isPOGSHlfI+CWMobSmvQrsj7YW01aWvl2pYIfV4LMd+C8or783yYrzKSK2JlP+Qw==} + testcontainers@11.13.0: + resolution: {integrity: sha512-fzTvgOtd6U/esOzgmDatJh79OSK0tU6vjDOJ3B6ICrrJf0dqCWtFdpOr6f/g/KixMxKDTDbszmZYjSORJXsVCQ==} text-decoder@1.2.3: resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} - text-extensions@2.4.0: - resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} - engines: {node: '>=8'} - text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -10034,15 +11417,12 @@ packages: resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} engines: {node: '>=18'} - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - thunky@1.1.0: resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==} - tightrope@0.2.0: - resolution: {integrity: sha512-Kw36UHxJEELq2VUqdaSGR2/8cAsPgMtvX8uGVU6Jk26O66PhXec0A5ZnRYs47btbtwPDpXXF66+Fo3vimCM9aQ==} - engines: {node: '>=16'} + time-span@4.0.0: + resolution: {integrity: sha512-MyqZCTGLDZ77u4k+jqg4UlrzPTPZ49NDlaekU6uuFaJLzPIN1woaRXCbGeqOfxwc3Y37ZROGAJ614Rdv7Olt+g==} + engines: {node: '>=10'} tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -10057,6 +11437,10 @@ packages: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} + tinyexec@1.1.1: + resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -10065,10 +11449,6 @@ packages: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} - tinyrainbow@3.0.3: - resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} - engines: {node: '>=14.0.0'} - tldts-core@6.1.86: resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} @@ -10087,6 +11467,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toidentifier@1.0.0: + resolution: {integrity: sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==} + engines: {node: '>=0.6'} + toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -10125,15 +11509,41 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-algebra@1.2.2: + resolution: {integrity: sha512-kloPhf1hq3JbCPOTYoOWDKxebWjNb2o/LKnNfkWhxVVisFFmMJPPdJeGoGmM+iRLyoXAR61e08Pb+vUXINg8aA==} + ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + ts-morph@12.0.0: + resolution: {integrity: sha512-VHC8XgU2fFW7yO1f/b3mxKDje1vmyzFXHWzOYmKEkCEwcLjDtbdLgBQviqj4ZwP4MJkQtRo6Ha2I29lq/B+VxA==} + + ts-node@10.9.1: + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true @@ -10148,8 +11558,8 @@ packages: '@swc/wasm': optional: true - ts-toolbelt@9.6.0: - resolution: {integrity: sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==} + ts-toolbelt@6.15.5: + resolution: {integrity: sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==} tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -10188,38 +11598,8 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - turbo-darwin-64@2.6.3: - resolution: {integrity: sha512-BlJJDc1CQ7SK5Y5qnl7AzpkvKSnpkfPmnA+HeU/sgny3oHZckPV2776ebO2M33CYDSor7+8HQwaodY++IINhYg==} - cpu: [x64] - os: [darwin] - - turbo-darwin-arm64@2.6.3: - resolution: {integrity: sha512-MwVt7rBKiOK7zdYerenfCRTypefw4kZCue35IJga9CH1+S50+KTiCkT6LBqo0hHeoH2iKuI0ldTF2a0aB72z3w==} - cpu: [arm64] - os: [darwin] - - turbo-linux-64@2.6.3: - resolution: {integrity: sha512-cqpcw+dXxbnPtNnzeeSyWprjmuFVpHJqKcs7Jym5oXlu/ZcovEASUIUZVN3OGEM6Y/OTyyw0z09tOHNt5yBAVg==} - cpu: [x64] - os: [linux] - - turbo-linux-arm64@2.6.3: - resolution: {integrity: sha512-MterpZQmjXyr4uM7zOgFSFL3oRdNKeflY7nsjxJb2TklsYqiu3Z9pQ4zRVFFH8n0mLGna7MbQMZuKoWqqHb45w==} - cpu: [arm64] - os: [linux] - - turbo-windows-64@2.6.3: - resolution: {integrity: sha512-biDU70v9dLwnBdLf+daoDlNJVvqOOP8YEjqNipBHzgclbQlXbsi6Gqqelp5er81Qo3BiRgmTNx79oaZQTPb07Q==} - cpu: [x64] - os: [win32] - - turbo-windows-arm64@2.6.3: - resolution: {integrity: sha512-dDHVKpSeukah3VsI/xMEKeTnV9V9cjlpFSUs4bmsUiLu3Yv2ENlgVEZv65wxbeE0bh0jjpmElDT+P1KaCxArQQ==} - cpu: [arm64] - os: [win32] - - turbo@2.6.3: - resolution: {integrity: sha512-bf6YKUv11l5Xfcmg76PyWoy/e2vbkkxFNBGJSnfdSXQC33ZiUfutYh6IXidc5MhsnrFkWfdNNLyaRk+kHMLlwA==} + turbo@2.9.4: + resolution: {integrity: sha512-wZ/kMcZCuK5oEp7sXSSo/5fzKjP9I2EhoiarZjyCm2Ixk0WxFrC/h0gF3686eHHINoFQOOSWgB/pGfvkR8rkgQ==} hasBin: true tweetnacl@0.14.5: @@ -10245,10 +11625,6 @@ packages: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} - type-fest@4.41.0: - resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} - engines: {node: '>=16'} - type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -10279,15 +11655,20 @@ packages: typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typescript-eslint@8.49.0: - resolution: {integrity: sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg==} + typescript-eslint@8.58.0: + resolution: {integrity: sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + typescript@6.0.2: + resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==} engines: {node: '>=14.17'} hasBin: true @@ -10303,6 +11684,9 @@ packages: engines: {node: '>=0.8.0'} hasBin: true + uid-promise@1.0.0: + resolution: {integrity: sha512-R8375j0qwXyIu/7R0tjdF06/sElHqbmdmWC9M2qQHpEVbvE4I5+38KJI7LUUmQMp7NVq4tKHiBMkT0NFM453Ig==} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -10313,11 +11697,15 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici-types@7.16.0: - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + undici@5.28.4: + resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} + engines: {node: '>=14.0'} - undici@7.16.0: - resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} + undici@7.24.7: + resolution: {integrity: sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==} engines: {node: '>=20.18.1'} unicode-canonical-property-names-ecmascript@2.0.1: @@ -10328,9 +11716,6 @@ packages: resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} engines: {node: '>=4'} - unicode-emoji-utils@1.3.1: - resolution: {integrity: sha512-6PiQxmnlsOsqzZCZz0sykSyMy/r1HiJiOWWXV98+BDva583DU4CtBeyDNsi4wMYUIbjUtMs4RgAuyft0EKLoVw==} - unicode-match-property-ecmascript@2.0.0: resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} engines: {node: '>=4'} @@ -10343,17 +11728,10 @@ packages: resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==} engines: {node: '>=4'} - unicorn-magic@0.1.0: - resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} - engines: {node: '>=18'} - unicorn-magic@0.3.0: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} - unified@10.1.2: - resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} - unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -10369,45 +11747,31 @@ packages: resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} engines: {node: '>=12'} - unist-util-generated@2.0.1: - resolution: {integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==} - - unist-util-is@5.2.1: - resolution: {integrity: sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==} - unist-util-is@6.0.1: resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} unist-util-position-from-estree@2.0.0: resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} - unist-util-position@4.0.4: - resolution: {integrity: sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==} - unist-util-position@5.0.0: resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} - unist-util-stringify-position@3.0.3: - resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==} - unist-util-stringify-position@4.0.0: resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - unist-util-visit-parents@5.1.3: - resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==} - unist-util-visit-parents@6.0.2: resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} - unist-util-visit@4.1.2: - resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==} - unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} universal-user-agent@7.0.3: resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -10465,9 +11829,6 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - util@0.10.4: - resolution: {integrity: sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==} - util@0.12.5: resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} @@ -10486,8 +11847,9 @@ packages: resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} hasBin: true - uuid@13.0.0: - resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} + uuid@3.3.2: + resolution: {integrity: sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==} + deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. hasBin: true uuid@8.0.0: @@ -10498,15 +11860,6 @@ packages: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true - uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - hasBin: true - - uvu@0.5.6: - resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} - engines: {node: '>=8'} - hasBin: true - v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -10520,10 +11873,6 @@ packages: validate-npm-package-name@3.0.0: resolution: {integrity: sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw==} - validate-npm-package-name@6.0.2: - resolution: {integrity: sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==} - engines: {node: ^18.17.0 || >=20.5.0} - validate-npm-package-name@7.0.0: resolution: {integrity: sha512-bwVk/OK+Qu108aJcMAEiU4yavHUI7aN20TgZNBj9MR2iU1zPUl1Z1Otr7771ExfYTPTvfN8ZJ1pbr5Iklgt4xg==} engines: {node: ^20.17.0 || >=22.9.0} @@ -10554,21 +11903,17 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - vfile-location@4.1.0: - resolution: {integrity: sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==} + vercel@39.4.2: + resolution: {integrity: sha512-A3ilkwJ83xLwAYAI733hHthJ4DO0zXjQOvCWS9QYklWQTBEj0RllyRkrfGd2jypgNDZuAbDS/iFMsV+GuuaTHw==} + engines: {node: '>= 16'} + hasBin: true vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} - vfile-message@3.1.4: - resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} - vfile-message@4.0.3: resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} - vfile@5.3.7: - resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==} - vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} @@ -10599,6 +11944,9 @@ packages: web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + web-vitals@0.2.4: + resolution: {integrity: sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg==} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -10704,6 +12052,10 @@ packages: resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} engines: {node: '>= 0.4'} + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -10734,10 +12086,6 @@ packages: wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -10764,22 +12112,14 @@ packages: resolution: {integrity: sha512-GmqrO8WJ1NuzJ2DrziEI2o57jKAVIQNf8a18W3nCYU3H7PNWqCCVTeH6/NQE93CIllIgQS98rrmVkYgTX9fFJQ==} engines: {node: ^18.17.0 || >=20.5.0} - write-file-atomic@7.0.0: - resolution: {integrity: sha512-YnlPC6JqnZl6aO4uRc+dx5PHguiR9S6WeoLtpxNT9wIG+BDya7ZNE1q7KOjVgaA73hKhKLpVPgJ5QA9THQ5BRg==} + write-file-atomic@7.0.1: + resolution: {integrity: sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==} engines: {node: ^20.17.0 || >=22.9.0} - write-json-file@6.0.0: - resolution: {integrity: sha512-MNHcU3f9WxnNyR6MxsYSj64Jz0+dwIpisWKWq9gqLj/GwmA9INg3BZ3vt70/HB3GEwrnDQWr4RPrywnhNzmUFA==} - engines: {node: '>=18'} - write-json-file@7.0.0: resolution: {integrity: sha512-rj8As6LkachKauGxvZkFzCEd6hIRTi9FKtCNKOa4SaH5vPOiACbGcmPUEJXgkhTHwzNsYmcSbD3C9a6whBfyOg==} engines: {node: '>=20'} - write-package@7.2.0: - resolution: {integrity: sha512-uMQTubF/vcu+Wd0b5BGtDmiXePd/+44hUWQz2nZPbs92/BnxRo74tqs+hqDo12RLiEd+CXFKUwxvvIZvtt34Jw==} - engines: {node: '>=18'} - ws@7.5.10: resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} engines: {node: '>=8.3.0'} @@ -10808,13 +12148,21 @@ packages: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} + xdg-app-paths@5.1.0: + resolution: {integrity: sha512-RAQ3WkPf4KTU1A8RtFx3gWywzVKe00tfOPFfl2NDGqbIFENQO4kqAJp7mhQjNj/33W5x5hiWWUdyfPq/5SU3QA==} + engines: {node: '>=6'} + xdg-basedir@5.1.0: resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} engines: {node: '>=12'} - xml-formatter@2.6.1: - resolution: {integrity: sha512-dOiGwoqm8y22QdTNI7A+N03tyVfBlQ0/oehAzxIZtwnFAHGeSlrfjF73YQvzSsa/Kt6+YZasKsrdu6OIpuBggw==} - engines: {node: '>= 10'} + xdg-portable@7.3.0: + resolution: {integrity: sha512-sqMMuL1rc0FmMBOzCpd0yuy9trqF2yTTVe+E9ogwCSWQCdDEtQUwrZPT6AxqtsFGRNxycgncbP/xmOOSPw5ZUw==} + engines: {node: '>= 6.0'} + + xml-formatter@3.7.0: + resolution: {integrity: sha512-+8qTc3zv2UcJ1v9IsSIce37Dl4MQG14Cp7tWrwmy202UaI1wqRukw5QMX1JHsV+DX64yw77EgGsj2s5wGvuMbQ==} + engines: {node: '>= 16'} xml-js@1.6.11: resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==} @@ -10824,9 +12172,9 @@ packages: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} - xml-parser-xo@3.2.0: - resolution: {integrity: sha512-8LRU6cq+d7mVsoDaMhnkkt3CTtAs4153p49fRo+HIB3I1FD1o5CeXRjRH29sQevIfVJIcPjKSsPU/+Ujhq09Rg==} - engines: {node: '>= 10'} + xml-parser-xo@4.1.5: + resolution: {integrity: sha512-TxyRxk9sTOUg3glxSIY6f0nfuqRll2OEF8TspLgh5mZkLuBgheCn3zClcDSGJ58TvNmiwyCCuat4UajPud/5Og==} + engines: {node: '>= 16'} xml2js@0.6.2: resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} @@ -10869,6 +12217,11 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yaml@2.8.3: + resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -10885,6 +12238,17 @@ packages: resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yauzl-clone@1.0.4: + resolution: {integrity: sha512-igM2RRCf3k8TvZoxR2oguuw4z1xasOnA31joCqHIyLkeWrvAc2Jgay5ISQ2ZplinkoGaJ6orCz56Ey456c5ESA==} + engines: {node: '>=6'} + + yauzl-promise@2.1.3: + resolution: {integrity: sha512-A1pf6fzh6eYkK0L4Qp7g9jzJSDrM6nN0bOn5T0IbY4Yo3w+YkWlHFkJP7mzknMXjqusHFHlKsK2N+4OLsK2MRA==} + engines: {node: '>=6'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} @@ -10897,10 +12261,6 @@ packages: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} - yoctocolors-cjs@2.1.3: - resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} - engines: {node: '>=18'} - yoctocolors@2.1.2: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} @@ -10912,10 +12272,10 @@ packages: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} - zod-to-json-schema@3.25.0: - resolution: {integrity: sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==} + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} peerDependencies: - zod: ^3.25 || ^4 + zod: ^3.25.28 || ^4 zod-validation-error@4.0.2: resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} @@ -10954,12 +12314,12 @@ snapshots: dependencies: json-schema: 0.4.0 - '@ai-sdk/react@2.0.120(react@19.2.3)(zod@4.1.13)': + '@ai-sdk/react@2.0.120(react@19.2.5)(zod@4.1.13)': dependencies: '@ai-sdk/provider-utils': 3.0.20(zod@4.1.13) ai: 5.0.118(zod@4.1.13) - react: 19.2.3 - swr: 2.3.8(react@19.2.3) + react: 19.2.5 + swr: 2.3.8(react@19.2.5) throttleit: 2.1.0 optionalDependencies: zod: 4.1.13 @@ -11007,88 +12367,588 @@ snapshots: '@algolia/requester-fetch': 5.46.2 '@algolia/requester-node-http': 5.46.2 - '@algolia/client-common@5.46.2': {} + '@algolia/client-common@5.46.2': {} + + '@algolia/client-insights@5.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 + + '@algolia/client-personalization@5.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 + + '@algolia/client-query-suggestions@5.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 + + '@algolia/client-search@5.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 + + '@algolia/events@4.0.1': {} + + '@algolia/ingestion@1.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 + + '@algolia/monitoring@1.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 + + '@algolia/recommend@5.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 + + '@algolia/requester-browser-xhr@5.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + + '@algolia/requester-fetch@5.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + + '@algolia/requester-node-http@5.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + + '@apidevtools/json-schema-ref-parser@15.3.5(@types/json-schema@7.0.15)': + dependencies: + '@types/json-schema': 7.0.15 + js-yaml: 4.1.1 + + '@asamuzakjp/css-color@3.2.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 10.4.3 + + '@asyncapi/specs@6.10.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@aws-crypto/crc32@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.7 + tslib: 2.8.1 + + '@aws-crypto/crc32c@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.7 + tslib: 2.8.1 + + '@aws-crypto/sha1-browser@5.2.0': + dependencies: + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-locate-window': 3.965.5 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-locate-window': 3.965.5 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.7 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-cloudformation@3.1029.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/credential-provider-node': 3.972.30 + '@aws-sdk/middleware-host-header': 3.972.9 + '@aws-sdk/middleware-logger': 3.972.9 + '@aws-sdk/middleware-recursion-detection': 3.972.10 + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/region-config-resolver': 3.972.11 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@aws-sdk/util-user-agent-browser': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.973.15 + '@smithy/config-resolver': 4.4.14 + '@smithy/core': 3.23.14 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/hash-node': 4.2.13 + '@smithy/invalid-dependency': 4.2.13 + '@smithy/middleware-content-length': 4.2.13 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-retry': 4.5.1 + '@smithy/middleware-serde': 4.2.17 + '@smithy/middleware-stack': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/node-http-handler': 4.5.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.45 + '@smithy/util-defaults-mode-node': 4.2.49 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/util-utf8': 4.2.2 + '@smithy/util-waiter': 4.2.15 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-s3@3.1029.0': + dependencies: + '@aws-crypto/sha1-browser': 5.2.0 + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/credential-provider-node': 3.972.30 + '@aws-sdk/middleware-bucket-endpoint': 3.972.9 + '@aws-sdk/middleware-expect-continue': 3.972.9 + '@aws-sdk/middleware-flexible-checksums': 3.974.7 + '@aws-sdk/middleware-host-header': 3.972.9 + '@aws-sdk/middleware-location-constraint': 3.972.9 + '@aws-sdk/middleware-logger': 3.972.9 + '@aws-sdk/middleware-recursion-detection': 3.972.10 + '@aws-sdk/middleware-sdk-s3': 3.972.28 + '@aws-sdk/middleware-ssec': 3.972.9 + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/region-config-resolver': 3.972.11 + '@aws-sdk/signature-v4-multi-region': 3.996.16 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@aws-sdk/util-user-agent-browser': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.973.15 + '@smithy/config-resolver': 4.4.14 + '@smithy/core': 3.23.14 + '@smithy/eventstream-serde-browser': 4.2.13 + '@smithy/eventstream-serde-config-resolver': 4.3.13 + '@smithy/eventstream-serde-node': 4.2.13 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/hash-blob-browser': 4.2.14 + '@smithy/hash-node': 4.2.13 + '@smithy/hash-stream-node': 4.2.13 + '@smithy/invalid-dependency': 4.2.13 + '@smithy/md5-js': 4.2.13 + '@smithy/middleware-content-length': 4.2.13 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-retry': 4.5.1 + '@smithy/middleware-serde': 4.2.17 + '@smithy/middleware-stack': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/node-http-handler': 4.5.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.45 + '@smithy/util-defaults-mode-node': 4.2.49 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/util-stream': 4.5.22 + '@smithy/util-utf8': 4.2.2 + '@smithy/util-waiter': 4.2.15 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/core@3.973.27': + dependencies: + '@aws-sdk/types': 3.973.7 + '@aws-sdk/xml-builder': 3.972.17 + '@smithy/core': 3.23.14 + '@smithy/node-config-provider': 4.3.13 + '@smithy/property-provider': 4.2.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/signature-v4': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/util-base64': 4.3.2 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/crc64-nvme@3.972.6': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.25': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/types': 3.973.7 + '@smithy/property-provider': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.27': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/types': 3.973.7 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/node-http-handler': 4.5.2 + '@smithy/property-provider': 4.2.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/util-stream': 4.5.22 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.972.29': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/credential-provider-env': 3.972.25 + '@aws-sdk/credential-provider-http': 3.972.27 + '@aws-sdk/credential-provider-login': 3.972.29 + '@aws-sdk/credential-provider-process': 3.972.25 + '@aws-sdk/credential-provider-sso': 3.972.29 + '@aws-sdk/credential-provider-web-identity': 3.972.29 + '@aws-sdk/nested-clients': 3.996.19 + '@aws-sdk/types': 3.973.7 + '@smithy/credential-provider-imds': 4.2.13 + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-login@3.972.29': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/nested-clients': 3.996.19 + '@aws-sdk/types': 3.973.7 + '@smithy/property-provider': 4.2.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-node@3.972.30': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.25 + '@aws-sdk/credential-provider-http': 3.972.27 + '@aws-sdk/credential-provider-ini': 3.972.29 + '@aws-sdk/credential-provider-process': 3.972.25 + '@aws-sdk/credential-provider-sso': 3.972.29 + '@aws-sdk/credential-provider-web-identity': 3.972.29 + '@aws-sdk/types': 3.973.7 + '@smithy/credential-provider-imds': 4.2.13 + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-process@3.972.25': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/types': 3.973.7 + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.972.29': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/nested-clients': 3.996.19 + '@aws-sdk/token-providers': 3.1026.0 + '@aws-sdk/types': 3.973.7 + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.972.29': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/nested-clients': 3.996.19 + '@aws-sdk/types': 3.973.7 + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/lib-storage@3.1029.0(@aws-sdk/client-s3@3.1029.0)': + dependencies: + '@aws-sdk/client-s3': 3.1029.0 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + buffer: 5.6.0 + events: 3.3.0 + stream-browserify: 3.0.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-bucket-endpoint@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-arn-parser': 3.972.3 + '@smithy/node-config-provider': 4.3.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-config-provider': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-expect-continue@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-flexible-checksums@3.974.7': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@aws-crypto/crc32c': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/crc64-nvme': 3.972.6 + '@aws-sdk/types': 3.973.7 + '@smithy/is-array-buffer': 4.2.2 + '@smithy/node-config-provider': 4.3.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-stream': 4.5.22 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-host-header@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-location-constraint@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-logger@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.7 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-recursion-detection@3.972.10': + dependencies: + '@aws-sdk/types': 3.973.7 + '@aws/lambda-invoke-store': 0.2.4 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.972.28': + dependencies: + '@aws-sdk/core': 3.973.27 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-arn-parser': 3.972.3 + '@smithy/core': 3.23.14 + '@smithy/node-config-provider': 4.3.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/signature-v4': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/util-config-provider': 4.2.2 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-stream': 4.5.22 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 - '@algolia/client-insights@5.46.2': + '@aws-sdk/middleware-ssec@3.972.9': dependencies: - '@algolia/client-common': 5.46.2 - '@algolia/requester-browser-xhr': 5.46.2 - '@algolia/requester-fetch': 5.46.2 - '@algolia/requester-node-http': 5.46.2 + '@aws-sdk/types': 3.973.7 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@algolia/client-personalization@5.46.2': + '@aws-sdk/middleware-user-agent@3.972.29': dependencies: - '@algolia/client-common': 5.46.2 - '@algolia/requester-browser-xhr': 5.46.2 - '@algolia/requester-fetch': 5.46.2 - '@algolia/requester-node-http': 5.46.2 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@smithy/core': 3.23.14 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-retry': 4.3.1 + tslib: 2.8.1 - '@algolia/client-query-suggestions@5.46.2': - dependencies: - '@algolia/client-common': 5.46.2 - '@algolia/requester-browser-xhr': 5.46.2 - '@algolia/requester-fetch': 5.46.2 - '@algolia/requester-node-http': 5.46.2 + '@aws-sdk/nested-clients@3.996.19': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/middleware-host-header': 3.972.9 + '@aws-sdk/middleware-logger': 3.972.9 + '@aws-sdk/middleware-recursion-detection': 3.972.10 + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/region-config-resolver': 3.972.11 + '@aws-sdk/types': 3.973.7 + '@aws-sdk/util-endpoints': 3.996.6 + '@aws-sdk/util-user-agent-browser': 3.972.9 + '@aws-sdk/util-user-agent-node': 3.973.15 + '@smithy/config-resolver': 4.4.14 + '@smithy/core': 3.23.14 + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/hash-node': 4.2.13 + '@smithy/invalid-dependency': 4.2.13 + '@smithy/middleware-content-length': 4.2.13 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-retry': 4.5.1 + '@smithy/middleware-serde': 4.2.17 + '@smithy/middleware-stack': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/node-http-handler': 4.5.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.45 + '@smithy/util-defaults-mode-node': 4.2.49 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt - '@algolia/client-search@5.46.2': + '@aws-sdk/region-config-resolver@3.972.11': dependencies: - '@algolia/client-common': 5.46.2 - '@algolia/requester-browser-xhr': 5.46.2 - '@algolia/requester-fetch': 5.46.2 - '@algolia/requester-node-http': 5.46.2 - - '@algolia/events@4.0.1': {} + '@aws-sdk/types': 3.973.7 + '@smithy/config-resolver': 4.4.14 + '@smithy/node-config-provider': 4.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@algolia/ingestion@1.46.2': + '@aws-sdk/signature-v4-multi-region@3.996.16': dependencies: - '@algolia/client-common': 5.46.2 - '@algolia/requester-browser-xhr': 5.46.2 - '@algolia/requester-fetch': 5.46.2 - '@algolia/requester-node-http': 5.46.2 + '@aws-sdk/middleware-sdk-s3': 3.972.28 + '@aws-sdk/types': 3.973.7 + '@smithy/protocol-http': 5.3.13 + '@smithy/signature-v4': 5.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@algolia/monitoring@1.46.2': + '@aws-sdk/token-providers@3.1026.0': dependencies: - '@algolia/client-common': 5.46.2 - '@algolia/requester-browser-xhr': 5.46.2 - '@algolia/requester-fetch': 5.46.2 - '@algolia/requester-node-http': 5.46.2 + '@aws-sdk/core': 3.973.27 + '@aws-sdk/nested-clients': 3.996.19 + '@aws-sdk/types': 3.973.7 + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt - '@algolia/recommend@5.46.2': + '@aws-sdk/types@3.973.7': dependencies: - '@algolia/client-common': 5.46.2 - '@algolia/requester-browser-xhr': 5.46.2 - '@algolia/requester-fetch': 5.46.2 - '@algolia/requester-node-http': 5.46.2 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@algolia/requester-browser-xhr@5.46.2': + '@aws-sdk/util-arn-parser@3.972.3': dependencies: - '@algolia/client-common': 5.46.2 + tslib: 2.8.1 - '@algolia/requester-fetch@5.46.2': + '@aws-sdk/util-endpoints@3.996.6': dependencies: - '@algolia/client-common': 5.46.2 + '@aws-sdk/types': 3.973.7 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-endpoints': 3.3.4 + tslib: 2.8.1 - '@algolia/requester-node-http@5.46.2': + '@aws-sdk/util-locate-window@3.965.5': dependencies: - '@algolia/client-common': 5.46.2 + tslib: 2.8.1 - '@apidevtools/json-schema-ref-parser@11.9.3': + '@aws-sdk/util-user-agent-browser@3.972.9': dependencies: - '@jsdevtools/ono': 7.1.3 - '@types/json-schema': 7.0.15 - js-yaml: 4.1.1 + '@aws-sdk/types': 3.973.7 + '@smithy/types': 4.14.0 + bowser: 2.14.1 + tslib: 2.8.1 - '@asamuzakjp/css-color@3.2.0': + '@aws-sdk/util-user-agent-node@3.973.15': dependencies: - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 - lru-cache: 10.4.3 + '@aws-sdk/middleware-user-agent': 3.972.29 + '@aws-sdk/types': 3.973.7 + '@smithy/node-config-provider': 4.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-config-provider': 4.2.2 + tslib: 2.8.1 - '@asyncapi/specs@6.10.0': + '@aws-sdk/xml-builder@3.972.17': dependencies: - '@types/json-schema': 7.0.15 + '@smithy/types': 4.14.0 + fast-xml-parser: 5.5.8 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.2.4': {} '@babel/code-frame@7.27.1': dependencies: @@ -11096,8 +12956,16 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/compat-data@7.28.5': {} + '@babel/compat-data@7.29.0': {} + '@babel/core@7.28.5': dependencies: '@babel/code-frame': 7.27.1 @@ -11118,6 +12986,26 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/generator@7.28.5': dependencies: '@babel/parser': 7.28.5 @@ -11126,6 +13014,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.27.3': dependencies: '@babel/types': 7.28.5 @@ -11138,31 +13034,52 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.5)': + '@babel/helper-compilation-targets@7.28.6': dependencies: - '@babel/core': 7.28.5 + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.29.0) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 '@babel/traverse': 7.28.5 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.28.5)': + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.29.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 regexpu-core: 6.4.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.5(@babel/core@7.28.5)': + '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 debug: 4.4.3 lodash.debounce: 4.0.8 resolve: 1.22.11 @@ -11185,6 +13102,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11194,30 +13118,59 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.28.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + '@babel/helper-optimise-call-expression@7.27.1': dependencies: '@babel/types': 7.28.5 '@babel/helper-plugin-utils@7.27.1': {} - '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.5)': + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-wrap-function': 7.28.3 '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.5)': + '@babel/helper-replace-supers@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: '@babel/traverse': 7.28.5 @@ -11244,57 +13197,66 @@ snapshots: '@babel/template': 7.27.2 '@babel/types': 7.28.5 + '@babel/helpers@7.29.2': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + '@babel/parser@7.28.5': dependencies: '@babel/types': 7.28.5 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.28.5)': + '@babel/parser@7.29.2': dependencies: - '@babel/core': 7.28.5 + '@babel/types': 7.29.0 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-transform-optional-chaining': 7.28.5(@babel/core@7.28.5) + '@babel/plugin-transform-optional-chaining': 7.28.5(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3(@babel/core@7.28.5)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.5 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-decorators@7.28.0(@babel/core@7.28.5)': + '@babel/plugin-proposal-decorators@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-decorators': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.5)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.5)': dependencies: @@ -11316,26 +13278,31 @@ snapshots: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11351,6 +13318,11 @@ snapshots: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -11396,512 +13368,529 @@ snapshots: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.5)': + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-async-generator-functions@7.28.0(@babel/core@7.28.5)': + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.5) - '@babel/traverse': 7.28.5 + + '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-block-scoping@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.28.3(@babel/core@7.28.5)': + '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.28.4(@babel/core@7.28.5)': + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-globals': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) - '@babel/traverse': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/template': 7.27.2 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/template': 7.28.6 - '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-dotall-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-explicit-resource-management@7.28.0(@babel/core@7.28.5)': + '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-exponentiation-operator@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-json-strings@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-literals@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-logical-assignment-operators@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-object-rest-spread@7.28.4(@babel/core@7.28.5)': + '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.5) - '@babel/traverse': 7.28.5 + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-optional-chaining@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-optional-chaining@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.28.5)': + '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-constant-elements@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-react-constant-elements@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.28.5)': + '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-module-imports': 7.27.1 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.0) '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-regenerator@7.28.4(@babel/core@7.28.5)': + '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regexp-modifiers@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-runtime@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-runtime@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 - babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.5) - babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.5) - babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-spread@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-unicode-property-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-unicode-sets-regex@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/preset-env@7.28.5(@babel/core@7.28.5)': + '@babel/preset-env@7.29.2(@babel/core@7.29.0)': dependencies: - '@babel/compat-data': 7.28.5 - '@babel/core': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/compat-data': 7.29.0 + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.3(@babel/core@7.28.5) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.5) - '@babel/plugin-syntax-import-assertions': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.28.5) - '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-async-generator-functions': 7.28.0(@babel/core@7.28.5) - '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-block-scoping': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-class-static-block': 7.28.3(@babel/core@7.28.5) - '@babel/plugin-transform-classes': 7.28.4(@babel/core@7.28.5) - '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-dotall-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-explicit-resource-management': 7.28.0(@babel/core@7.28.5) - '@babel/plugin-transform-exponentiation-operator': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-json-strings': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-logical-assignment-operators': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-modules-systemjs': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-numeric-separator': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-object-rest-spread': 7.28.4(@babel/core@7.28.5) - '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-optional-chaining': 7.28.5(@babel/core@7.28.5) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.5) - '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-regenerator': 7.28.4(@babel/core@7.28.5) - '@babel/plugin-transform-regexp-modifiers': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-spread': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-unicode-property-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-unicode-sets-regex': 7.27.1(@babel/core@7.28.5) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.28.5) - babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.5) - babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.5) - babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.5) - core-js-compat: 3.47.0 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0) + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) + core-js-compat: 3.49.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.5)': + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 '@babel/types': 7.28.5 esutils: 2.0.3 - '@babel/preset-react@7.28.5(@babel/core@7.28.5)': + '@babel/preset-react@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.5) - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.28.5(@babel/core@7.28.5)': + '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5) + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/runtime-corejs3@7.28.4': - dependencies: - core-js-pure: 3.47.0 - '@babel/runtime@7.28.4': {} '@babel/template@7.27.2': @@ -11910,6 +13899,12 @@ snapshots: '@babel/parser': 7.28.5 '@babel/types': 7.28.5 + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@babel/traverse@7.28.5': dependencies: '@babel/code-frame': 7.27.1 @@ -11922,11 +13917,28 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@babel/types@7.28.5': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@balena/dockerignore@1.0.2': {} '@bcoe/v8-coverage@0.2.3': {} @@ -11934,32 +13946,34 @@ snapshots: '@colors/colors@1.5.0': optional: true - '@commitlint/cli@20.2.0(@types/node@25.0.3)(typescript@5.9.3)': + '@commitlint/cli@20.5.0(@types/node@25.5.2)(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)(typescript@6.0.2)': dependencies: - '@commitlint/format': 20.2.0 - '@commitlint/lint': 20.2.0 - '@commitlint/load': 20.2.0(@types/node@25.0.3)(typescript@5.9.3) - '@commitlint/read': 20.2.0 - '@commitlint/types': 20.2.0 + '@commitlint/format': 20.5.0 + '@commitlint/lint': 20.5.0 + '@commitlint/load': 20.5.0(@types/node@25.5.2)(typescript@6.0.2) + '@commitlint/read': 20.5.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0) + '@commitlint/types': 20.5.0 tinyexec: 1.0.2 yargs: 17.7.2 transitivePeerDependencies: - '@types/node' + - conventional-commits-filter + - conventional-commits-parser - typescript - '@commitlint/config-conventional@19.8.1': + '@commitlint/config-conventional@20.5.0': dependencies: - '@commitlint/types': 19.8.1 - conventional-changelog-conventionalcommits: 7.0.2 + '@commitlint/types': 20.5.0 + conventional-changelog-conventionalcommits: 9.3.1 - '@commitlint/config-validator@20.2.0': + '@commitlint/config-validator@20.5.0': dependencies: - '@commitlint/types': 20.2.0 + '@commitlint/types': 20.5.0 ajv: 8.17.1 - '@commitlint/ensure@20.2.0': + '@commitlint/ensure@20.5.0': dependencies: - '@commitlint/types': 20.2.0 + '@commitlint/types': 20.5.0 lodash.camelcase: 4.3.0 lodash.kebabcase: 4.1.1 lodash.snakecase: 4.1.1 @@ -11968,100 +13982,96 @@ snapshots: '@commitlint/execute-rule@20.0.0': {} - '@commitlint/format@20.2.0': + '@commitlint/format@20.5.0': dependencies: - '@commitlint/types': 20.2.0 - chalk: 5.6.2 + '@commitlint/types': 20.5.0 + picocolors: 1.1.1 - '@commitlint/is-ignored@20.2.0': + '@commitlint/is-ignored@20.5.0': dependencies: - '@commitlint/types': 20.2.0 - semver: 7.7.3 + '@commitlint/types': 20.5.0 + semver: 7.7.4 - '@commitlint/lint@20.2.0': + '@commitlint/lint@20.5.0': dependencies: - '@commitlint/is-ignored': 20.2.0 - '@commitlint/parse': 20.2.0 - '@commitlint/rules': 20.2.0 - '@commitlint/types': 20.2.0 + '@commitlint/is-ignored': 20.5.0 + '@commitlint/parse': 20.5.0 + '@commitlint/rules': 20.5.0 + '@commitlint/types': 20.5.0 - '@commitlint/load@20.2.0(@types/node@25.0.3)(typescript@5.9.3)': + '@commitlint/load@20.5.0(@types/node@25.5.2)(typescript@6.0.2)': dependencies: - '@commitlint/config-validator': 20.2.0 + '@commitlint/config-validator': 20.5.0 '@commitlint/execute-rule': 20.0.0 - '@commitlint/resolve-extends': 20.2.0 - '@commitlint/types': 20.2.0 - chalk: 5.6.2 - cosmiconfig: 9.0.0(typescript@5.9.3) - cosmiconfig-typescript-loader: 6.2.0(@types/node@25.0.3)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3) - lodash.isplainobject: 4.0.6 - lodash.merge: 4.6.2 - lodash.uniq: 4.5.0 + '@commitlint/resolve-extends': 20.5.0 + '@commitlint/types': 20.5.0 + cosmiconfig: 9.0.1(typescript@6.0.2) + cosmiconfig-typescript-loader: 6.2.0(@types/node@25.5.2)(cosmiconfig@9.0.1(typescript@6.0.2))(typescript@6.0.2) + is-plain-obj: 4.1.0 + lodash.mergewith: 4.6.2 + picocolors: 1.1.1 transitivePeerDependencies: - '@types/node' - typescript - '@commitlint/message@20.0.0': {} + '@commitlint/message@20.4.3': {} - '@commitlint/parse@20.2.0': + '@commitlint/parse@20.5.0': dependencies: - '@commitlint/types': 20.2.0 - conventional-changelog-angular: 7.0.0 - conventional-commits-parser: 5.0.0 + '@commitlint/types': 20.5.0 + conventional-changelog-angular: 8.3.1 + conventional-commits-parser: 6.4.0 - '@commitlint/read@20.2.0': + '@commitlint/read@20.5.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)': dependencies: - '@commitlint/top-level': 20.0.0 - '@commitlint/types': 20.2.0 - git-raw-commits: 4.0.0 + '@commitlint/top-level': 20.4.3 + '@commitlint/types': 20.5.0 + git-raw-commits: 5.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0) minimist: 1.2.8 tinyexec: 1.0.2 + transitivePeerDependencies: + - conventional-commits-filter + - conventional-commits-parser - '@commitlint/resolve-extends@20.2.0': + '@commitlint/resolve-extends@20.5.0': dependencies: - '@commitlint/config-validator': 20.2.0 - '@commitlint/types': 20.2.0 + '@commitlint/config-validator': 20.5.0 + '@commitlint/types': 20.5.0 global-directory: 4.0.1 import-meta-resolve: 4.2.0 lodash.mergewith: 4.6.2 resolve-from: 5.0.0 - '@commitlint/rules@20.2.0': + '@commitlint/rules@20.5.0': dependencies: - '@commitlint/ensure': 20.2.0 - '@commitlint/message': 20.0.0 + '@commitlint/ensure': 20.5.0 + '@commitlint/message': 20.4.3 '@commitlint/to-lines': 20.0.0 - '@commitlint/types': 20.2.0 + '@commitlint/types': 20.5.0 '@commitlint/to-lines@20.0.0': {} - '@commitlint/top-level@20.0.0': - dependencies: - find-up: 7.0.0 - - '@commitlint/types@19.8.1': + '@commitlint/top-level@20.4.3': dependencies: - '@types/conventional-commits-parser': 5.0.2 - chalk: 5.6.2 + escalade: 3.2.0 - '@commitlint/types@20.2.0': + '@commitlint/types@20.5.0': dependencies: - '@types/conventional-commits-parser': 5.0.2 - chalk: 5.6.2 + conventional-commits-parser: 6.4.0 + picocolors: 1.1.1 - '@conventional-changelog/git-client@2.5.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.2.1)': + '@conventional-changelog/git-client@2.6.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)': dependencies: '@simple-libs/child-process-utils': 1.0.1 - '@simple-libs/stream-utils': 1.1.0 - semver: 7.7.3 + '@simple-libs/stream-utils': 1.2.0 + semver: 7.7.4 optionalDependencies: conventional-commits-filter: 5.0.0 - conventional-commits-parser: 6.2.1 + conventional-commits-parser: 6.4.0 '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 - optional: true '@csstools/cascade-layer-name-parser@2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: @@ -12374,19 +14384,19 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} - '@docsearch/core@4.4.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@docsearch/core@4.4.0(@types/react@19.2.7)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': optionalDependencies: '@types/react': 19.2.7 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) '@docsearch/css@4.4.0': {} - '@docsearch/react@4.4.0(@algolia/client-search@5.46.2)(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(search-insights@2.17.3)': + '@docsearch/react@4.4.0(@algolia/client-search@5.46.2)(@types/react@19.2.7)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(search-insights@2.17.3)': dependencies: - '@ai-sdk/react': 2.0.120(react@19.2.3)(zod@4.1.13) + '@ai-sdk/react': 2.0.120(react@19.2.5)(zod@4.1.13) '@algolia/autocomplete-core': 1.19.2(@algolia/client-search@5.46.2)(algoliasearch@5.46.2)(search-insights@2.17.3) - '@docsearch/core': 4.4.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@docsearch/core': 4.4.0(@types/react@19.2.7)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@docsearch/css': 4.4.0 ai: 5.0.118(zod@4.1.13) algoliasearch: 5.46.2 @@ -12394,28 +14404,27 @@ snapshots: zod: 4.1.13 optionalDependencies: '@types/react': 19.2.7 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) search-insights: 2.17.3 transitivePeerDependencies: - '@algolia/client-search' - '@docusaurus/babel@3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@docusaurus/babel@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: - '@babel/core': 7.28.5 - '@babel/generator': 7.28.5 - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.28.5) - '@babel/preset-env': 7.28.5(@babel/core@7.28.5) - '@babel/preset-react': 7.28.5(@babel/core@7.28.5) - '@babel/preset-typescript': 7.28.5(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.29.0) + '@babel/preset-env': 7.29.2(@babel/core@7.29.0) + '@babel/preset-react': 7.28.5(@babel/core@7.29.0) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) '@babel/runtime': 7.28.4 - '@babel/runtime-corejs3': 7.28.4 - '@babel/traverse': 7.28.5 - '@docusaurus/logger': 3.9.2 - '@docusaurus/utils': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@babel/traverse': 7.29.0 + '@docusaurus/logger': 3.10.0 + '@docusaurus/utils': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) babel-plugin-dynamic-import-node: 2.3.3 - fs-extra: 11.3.2 + fs-extra: 11.3.4 tslib: 2.8.1 transitivePeerDependencies: - '@swc/core' @@ -12426,32 +14435,34 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/bundler@3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': + '@docusaurus/bundler@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2)': dependencies: - '@babel/core': 7.28.5 - '@docusaurus/babel': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/cssnano-preset': 3.9.2 - '@docusaurus/logger': 3.9.2 - '@docusaurus/types': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - babel-loader: 9.2.1(@babel/core@7.28.5)(webpack@5.104.1) + '@babel/core': 7.29.0 + '@docusaurus/babel': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/cssnano-preset': 3.10.0 + '@docusaurus/logger': 3.10.0 + '@docusaurus/types': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + babel-loader: 9.2.1(@babel/core@7.29.0)(webpack@5.104.1(@swc/core@1.15.24)) clean-css: 5.3.3 - copy-webpack-plugin: 11.0.0(webpack@5.104.1) - css-loader: 6.11.0(webpack@5.104.1) - css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(webpack@5.104.1) + copy-webpack-plugin: 11.0.0(webpack@5.104.1(@swc/core@1.15.24)) + css-loader: 6.11.0(@rspack/core@1.7.11)(webpack@5.104.1(@swc/core@1.15.24)) + css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(webpack@5.104.1(@swc/core@1.15.24)) cssnano: 6.1.2(postcss@8.5.6) - file-loader: 6.2.0(webpack@5.104.1) + file-loader: 6.2.0(webpack@5.104.1(@swc/core@1.15.24)) html-minifier-terser: 7.2.0 - mini-css-extract-plugin: 2.9.4(webpack@5.104.1) - null-loader: 4.0.1(webpack@5.104.1) + mini-css-extract-plugin: 2.9.4(webpack@5.104.1(@swc/core@1.15.24)) + null-loader: 4.0.1(webpack@5.104.1(@swc/core@1.15.24)) postcss: 8.5.6 - postcss-loader: 7.3.4(postcss@8.5.6)(typescript@5.9.3)(webpack@5.104.1) + postcss-loader: 7.3.4(postcss@8.5.6)(typescript@6.0.2)(webpack@5.104.1(@swc/core@1.15.24)) postcss-preset-env: 10.6.0(postcss@8.5.6) - terser-webpack-plugin: 5.3.16(webpack@5.104.1) + terser-webpack-plugin: 5.3.16(@swc/core@1.15.24)(webpack@5.104.1(@swc/core@1.15.24)) tslib: 2.8.1 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.104.1))(webpack@5.104.1) - webpack: 5.104.1 - webpackbar: 6.0.1(webpack@5.104.1) + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.104.1(@swc/core@1.15.24)))(webpack@5.104.1(@swc/core@1.15.24)) + webpack: 5.104.1(@swc/core@1.15.24) + webpackbar: 6.0.1(webpack@5.104.1(@swc/core@1.15.24)) + optionalDependencies: + '@docusaurus/faster': 3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) transitivePeerDependencies: - '@parcel/css' - '@rspack/core' @@ -12467,16 +14478,16 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/core@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': + '@docusaurus/core@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2)': dependencies: - '@docusaurus/babel': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/bundler': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/logger': 3.9.2 - '@docusaurus/mdx-loader': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-common': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-validation': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@mdx-js/react': 3.1.1(@types/react@19.2.7)(react@19.2.3) + '@docusaurus/babel': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/bundler': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/logger': 3.10.0 + '@docusaurus/mdx-loader': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-common': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-validation': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@mdx-js/react': 3.1.1(@types/react@19.2.7)(react@19.2.5) boxen: 6.2.1 chalk: 4.1.2 chokidar: 3.6.0 @@ -12489,33 +14500,34 @@ snapshots: eta: 2.2.0 eval: 0.1.8 execa: 5.1.1 - fs-extra: 11.3.2 + fs-extra: 11.3.4 html-tags: 3.3.1 - html-webpack-plugin: 5.6.5(webpack@5.104.1) + html-webpack-plugin: 5.6.5(@rspack/core@1.7.11)(webpack@5.104.1(@swc/core@1.15.24)) leven: 3.1.0 - lodash: 4.17.21 + lodash: 4.17.23 open: 8.4.2 p-map: 4.0.0 prompts: 2.4.2 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)' - react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.3)' - react-loadable-ssr-addon-v5-slorber: 1.0.1(@docusaurus/react-loadable@6.0.0(react@19.2.3))(webpack@5.104.1) - react-router: 5.3.4(react@19.2.3) - react-router-config: 5.1.1(react-router@5.3.4(react@19.2.3))(react@19.2.3) - react-router-dom: 5.3.4(react@19.2.3) - semver: 7.7.3 - serve-handler: 6.1.6 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)' + react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.5)' + react-loadable-ssr-addon-v5-slorber: 1.0.3(@docusaurus/react-loadable@6.0.0(react@19.2.5))(webpack@5.104.1(@swc/core@1.15.24)) + react-router: 5.3.4(react@19.2.5) + react-router-config: 5.1.1(react-router@5.3.4(react@19.2.5))(react@19.2.5) + react-router-dom: 5.3.4(react@19.2.5) + semver: 7.7.4 + serve-handler: 6.1.7 tinypool: 1.1.1 tslib: 2.8.1 update-notifier: 6.0.2 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) webpack-bundle-analyzer: 4.10.2 - webpack-dev-server: 5.2.2(webpack@5.104.1) + webpack-dev-server: 5.2.2(webpack@5.104.1(@swc/core@1.15.24)) webpack-merge: 6.0.1 + optionalDependencies: + '@docusaurus/faster': 3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) transitivePeerDependencies: - - '@docusaurus/faster' - '@parcel/css' - '@rspack/core' - '@swc/core' @@ -12531,34 +14543,52 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/cssnano-preset@3.9.2': + '@docusaurus/cssnano-preset@3.10.0': dependencies: cssnano-preset-advanced: 6.1.2(postcss@8.5.6) postcss: 8.5.6 postcss-sort-media-queries: 5.2.0(postcss@8.5.6) tslib: 2.8.1 - '@docusaurus/logger@3.9.2': + '@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))': + dependencies: + '@docusaurus/types': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@rspack/core': 1.7.11 + '@swc/core': 1.15.24 + '@swc/html': 1.15.24 + browserslist: 4.28.1 + lightningcss: 1.32.0 + semver: 7.7.4 + swc-loader: 0.2.7(@swc/core@1.15.24)(webpack@5.104.1(@swc/core@1.15.24)) + tslib: 2.8.1 + webpack: 5.104.1(@swc/core@1.15.24) + transitivePeerDependencies: + - '@swc/helpers' + - esbuild + - uglify-js + - webpack-cli + + '@docusaurus/logger@3.10.0': dependencies: chalk: 4.1.2 tslib: 2.8.1 - '@docusaurus/mdx-loader@3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@docusaurus/mdx-loader@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: - '@docusaurus/logger': 3.9.2 - '@docusaurus/utils': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-validation': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@docusaurus/logger': 3.10.0 + '@docusaurus/utils': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-validation': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@mdx-js/mdx': 3.1.1 '@slorber/remark-comment': 1.0.0 escape-html: 1.0.3 estree-util-value-to-estree: 3.5.0 - file-loader: 6.2.0(webpack@5.104.1) - fs-extra: 11.3.2 + file-loader: 6.2.0(webpack@5.104.1(@swc/core@1.15.24)) + fs-extra: 11.3.4 image-size: 2.0.2 mdast-util-mdx: 3.0.0 mdast-util-to-string: 4.0.0 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) rehype-raw: 7.0.0 remark-directive: 3.0.1 remark-emoji: 4.0.1 @@ -12568,9 +14598,9 @@ snapshots: tslib: 2.8.1 unified: 11.0.5 unist-util-visit: 5.0.0 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.104.1))(webpack@5.104.1) + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.104.1(@swc/core@1.15.24)))(webpack@5.104.1(@swc/core@1.15.24)) vfile: 6.0.3 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) transitivePeerDependencies: - '@swc/core' - esbuild @@ -12578,17 +14608,17 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/module-type-aliases@3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@docusaurus/module-type-aliases@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: - '@docusaurus/types': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@docusaurus/types': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@types/history': 4.7.11 '@types/react': 19.2.7 '@types/react-router-config': 5.0.11 '@types/react-router-dom': 5.3.3 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)' - react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.3)' + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)' + react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.5)' transitivePeerDependencies: - '@swc/core' - esbuild @@ -12596,29 +14626,30 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/plugin-content-blog@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': - dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/logger': 3.9.2 - '@docusaurus/mdx-loader': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/types': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-common': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-validation': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@docusaurus/plugin-content-blog@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@docusaurus/plugin-content-docs@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2)': + dependencies: + '@docusaurus/core': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/logger': 3.10.0 + '@docusaurus/mdx-loader': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/plugin-content-docs': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/theme-common': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2))(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/types': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-common': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-validation': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) cheerio: 1.0.0-rc.12 + combine-promises: 1.2.0 feed: 4.2.2 - fs-extra: 11.3.2 - lodash: 4.17.21 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + fs-extra: 11.3.4 + lodash: 4.17.23 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) schema-dts: 1.1.5 srcset: 4.0.0 tslib: 2.8.1 unist-util-visit: 5.0.0 utility-types: 3.11.0 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' @@ -12637,28 +14668,28 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': - dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/logger': 3.9.2 - '@docusaurus/mdx-loader': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/module-type-aliases': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/types': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-common': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-validation': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@docusaurus/plugin-content-docs@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2)': + dependencies: + '@docusaurus/core': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/logger': 3.10.0 + '@docusaurus/mdx-loader': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/module-type-aliases': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/theme-common': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2))(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/types': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-common': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-validation': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@types/react-router-config': 5.0.11 combine-promises: 1.2.0 - fs-extra: 11.3.2 + fs-extra: 11.3.4 js-yaml: 4.1.1 - lodash: 4.17.21 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + lodash: 4.17.23 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) schema-dts: 1.1.5 tslib: 2.8.1 utility-types: 3.11.0 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' @@ -12677,18 +14708,18 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-pages@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': + '@docusaurus/plugin-content-pages@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/mdx-loader': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/types': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-validation': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - fs-extra: 11.3.2 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + '@docusaurus/core': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/mdx-loader': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/types': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-validation': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + fs-extra: 11.3.4 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) tslib: 2.8.1 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' @@ -12707,12 +14738,12 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-css-cascade-layers@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': + '@docusaurus/plugin-css-cascade-layers@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/types': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-validation': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@docusaurus/core': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/types': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-validation': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' @@ -12734,15 +14765,15 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-debug@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': + '@docusaurus/plugin-debug@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/types': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - fs-extra: 11.3.2 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - react-json-view-lite: 2.5.0(react@19.2.3) + '@docusaurus/core': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/types': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + fs-extra: 11.3.4 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + react-json-view-lite: 2.5.0(react@19.2.5) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' @@ -12762,13 +14793,13 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-analytics@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': + '@docusaurus/plugin-google-analytics@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/types': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-validation': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + '@docusaurus/core': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/types': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-validation': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' @@ -12788,14 +14819,14 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-gtag@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': + '@docusaurus/plugin-google-gtag@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/types': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-validation': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@types/gtag.js': 0.0.12 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + '@docusaurus/core': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/types': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-validation': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@types/gtag.js': 0.0.20 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' @@ -12815,13 +14846,13 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-tag-manager@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': + '@docusaurus/plugin-google-tag-manager@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/types': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-validation': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + '@docusaurus/core': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/types': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-validation': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/faster' @@ -12841,17 +14872,17 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-sitemap@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': - dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/logger': 3.9.2 - '@docusaurus/types': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-common': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-validation': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - fs-extra: 11.3.2 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + '@docusaurus/plugin-sitemap@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2)': + dependencies: + '@docusaurus/core': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/logger': 3.10.0 + '@docusaurus/types': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-common': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-validation': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + fs-extra: 11.3.4 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) sitemap: 7.1.2 tslib: 2.8.1 transitivePeerDependencies: @@ -12872,18 +14903,18 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-svgr@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': + '@docusaurus/plugin-svgr@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2)': dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/types': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-validation': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@svgr/core': 8.1.0(typescript@5.9.3) - '@svgr/webpack': 8.1.0(typescript@5.9.3) - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + '@docusaurus/core': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/types': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-validation': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@svgr/core': 8.1.0(typescript@6.0.2) + '@svgr/webpack': 8.1.0(typescript@6.0.2) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) tslib: 2.8.1 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) transitivePeerDependencies: - '@docusaurus/faster' - '@mdx-js/react' @@ -12902,25 +14933,25 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/preset-classic@3.9.2(@algolia/client-search@5.46.2)(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(search-insights@2.17.3)(typescript@5.9.3)': - dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/plugin-content-pages': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/plugin-css-cascade-layers': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/plugin-debug': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/plugin-google-analytics': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/plugin-google-gtag': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/plugin-google-tag-manager': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/plugin-sitemap': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/plugin-svgr': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/theme-classic': 3.9.2(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/theme-search-algolia': 3.9.2(@algolia/client-search@5.46.2)(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(search-insights@2.17.3)(typescript@5.9.3) - '@docusaurus/types': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + '@docusaurus/preset-classic@3.10.0(@algolia/client-search@5.46.2)(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(@types/react@19.2.7)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(search-insights@2.17.3)(typescript@6.0.2)': + dependencies: + '@docusaurus/core': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/plugin-content-blog': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@docusaurus/plugin-content-docs@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/plugin-content-docs': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/plugin-content-pages': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/plugin-css-cascade-layers': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/plugin-debug': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/plugin-google-analytics': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/plugin-google-gtag': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/plugin-google-tag-manager': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/plugin-sitemap': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/plugin-svgr': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/theme-classic': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@rspack/core@1.7.11)(@swc/core@1.15.24)(@types/react@19.2.7)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/theme-common': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2))(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/theme-search-algolia': 3.10.0(@algolia/client-search@5.46.2)(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(@types/react@19.2.7)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(search-insights@2.17.3)(typescript@6.0.2) + '@docusaurus/types': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) transitivePeerDependencies: - '@algolia/client-search' - '@docusaurus/faster' @@ -12942,37 +14973,38 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/react-loadable@6.0.0(react@19.2.3)': + '@docusaurus/react-loadable@6.0.0(react@19.2.5)': dependencies: '@types/react': 19.2.7 - react: 19.2.3 - - '@docusaurus/theme-classic@3.9.2(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': - dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/logger': 3.9.2 - '@docusaurus/mdx-loader': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/module-type-aliases': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/plugin-content-pages': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/theme-translations': 3.9.2 - '@docusaurus/types': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-common': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-validation': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@mdx-js/react': 3.1.1(@types/react@19.2.7)(react@19.2.3) + react: 19.2.5 + + '@docusaurus/theme-classic@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@rspack/core@1.7.11)(@swc/core@1.15.24)(@types/react@19.2.7)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2)': + dependencies: + '@docusaurus/core': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/logger': 3.10.0 + '@docusaurus/mdx-loader': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/module-type-aliases': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/plugin-content-blog': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@docusaurus/plugin-content-docs@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/plugin-content-docs': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/plugin-content-pages': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/theme-common': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2))(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/theme-translations': 3.10.0 + '@docusaurus/types': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-common': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-validation': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@mdx-js/react': 3.1.1(@types/react@19.2.7)(react@19.2.5) clsx: 2.1.1 + copy-text-to-clipboard: 3.2.2 infima: 0.2.0-alpha.45 - lodash: 4.17.21 + lodash: 4.17.23 nprogress: 0.2.0 postcss: 8.5.6 - prism-react-renderer: 2.4.1(react@19.2.3) + prism-react-renderer: 2.4.1(react@19.2.5) prismjs: 1.30.0 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - react-router-dom: 5.3.4(react@19.2.3) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + react-router-dom: 5.3.4(react@19.2.5) rtlcss: 4.3.0 tslib: 2.8.1 utility-types: 3.11.0 @@ -12994,21 +15026,21 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@docusaurus/theme-common@3.10.0(@docusaurus/plugin-content-docs@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2))(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: - '@docusaurus/mdx-loader': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/module-type-aliases': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/utils': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-common': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@docusaurus/mdx-loader': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/module-type-aliases': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/plugin-content-docs': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/utils': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-common': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@types/history': 4.7.11 '@types/react': 19.2.7 '@types/react-router-config': 5.0.11 clsx: 2.1.1 parse-numeric-range: 1.3.0 - prism-react-renderer: 2.4.1(react@19.2.3) - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + prism-react-renderer: 2.4.1(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) tslib: 2.8.1 utility-types: 3.11.0 transitivePeerDependencies: @@ -13018,24 +15050,25 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/theme-search-algolia@3.9.2(@algolia/client-search@5.46.2)(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(search-insights@2.17.3)(typescript@5.9.3)': + '@docusaurus/theme-search-algolia@3.10.0(@algolia/client-search@5.46.2)(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(@types/react@19.2.7)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(search-insights@2.17.3)(typescript@6.0.2)': dependencies: - '@docsearch/react': 4.4.0(@algolia/client-search@5.46.2)(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(search-insights@2.17.3) - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/logger': 3.9.2 - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/theme-translations': 3.9.2 - '@docusaurus/utils': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-validation': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@algolia/autocomplete-core': 1.19.2(@algolia/client-search@5.46.2)(algoliasearch@5.46.2)(search-insights@2.17.3) + '@docsearch/react': 4.4.0(@algolia/client-search@5.46.2)(@types/react@19.2.7)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(search-insights@2.17.3) + '@docusaurus/core': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/logger': 3.10.0 + '@docusaurus/plugin-content-docs': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/theme-common': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2))(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/theme-translations': 3.10.0 + '@docusaurus/utils': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-validation': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) algoliasearch: 5.46.2 algoliasearch-helper: 3.27.0(algoliasearch@5.46.2) clsx: 2.1.1 eta: 2.2.0 - fs-extra: 11.3.2 - lodash: 4.17.21 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + fs-extra: 11.3.4 + lodash: 4.17.23 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) tslib: 2.8.1 utility-types: 3.11.0 transitivePeerDependencies: @@ -13059,14 +15092,14 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/theme-translations@3.9.2': + '@docusaurus/theme-translations@3.10.0': dependencies: - fs-extra: 11.3.2 + fs-extra: 11.3.4 tslib: 2.8.1 - '@docusaurus/tsconfig@3.9.2': {} + '@docusaurus/tsconfig@3.10.0': {} - '@docusaurus/types@3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@mdx-js/mdx': 3.1.1 '@types/history': 4.7.11 @@ -13074,11 +15107,11 @@ snapshots: '@types/react': 19.2.7 commander: 5.1.0 joi: 17.13.3 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)' + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)' utility-types: 3.11.0 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) webpack-merge: 5.10.0 transitivePeerDependencies: - '@swc/core' @@ -13087,9 +15120,9 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils-common@3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@docusaurus/utils-common@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: - '@docusaurus/types': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@docusaurus/types': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) tslib: 2.8.1 transitivePeerDependencies: - '@swc/core' @@ -13100,15 +15133,15 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils-validation@3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@docusaurus/utils-validation@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: - '@docusaurus/logger': 3.9.2 - '@docusaurus/utils': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-common': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - fs-extra: 11.3.2 + '@docusaurus/logger': 3.10.0 + '@docusaurus/utils': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-common': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + fs-extra: 11.3.4 joi: 17.13.3 js-yaml: 4.1.1 - lodash: 4.17.21 + lodash: 4.17.23 tslib: 2.8.1 transitivePeerDependencies: - '@swc/core' @@ -13119,29 +15152,29 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils@3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@docusaurus/utils@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: - '@docusaurus/logger': 3.9.2 - '@docusaurus/types': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-common': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@docusaurus/logger': 3.10.0 + '@docusaurus/types': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-common': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) escape-string-regexp: 4.0.0 execa: 5.1.1 - file-loader: 6.2.0(webpack@5.104.1) - fs-extra: 11.3.2 + file-loader: 6.2.0(webpack@5.104.1(@swc/core@1.15.24)) + fs-extra: 11.3.4 github-slugger: 1.5.0 globby: 11.1.0 gray-matter: 4.0.3 jiti: 1.21.7 js-yaml: 4.1.1 - lodash: 4.17.21 + lodash: 4.17.23 micromatch: 4.0.8 p-queue: 6.6.2 prompts: 2.4.2 resolve-pathname: 3.0.0 tslib: 2.8.1 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.104.1))(webpack@5.104.1) + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.104.1(@swc/core@1.15.24)))(webpack@5.104.1(@swc/core@1.15.24)) utility-types: 3.11.0 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) transitivePeerDependencies: - '@swc/core' - esbuild @@ -13151,6 +15184,18 @@ snapshots: - uglify-js - webpack-cli + '@edge-runtime/format@2.2.1': {} + + '@edge-runtime/node-utils@2.3.0': {} + + '@edge-runtime/ponyfill@2.4.2': {} + + '@edge-runtime/primitives@4.1.0': {} + + '@edge-runtime/vm@3.2.0': + dependencies: + '@edge-runtime/primitives': 4.1.0 + '@emnapi/core@1.7.1': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -13187,91 +15232,174 @@ snapshots: '@esbuild/aix-ppc64@0.27.1': optional: true + '@esbuild/aix-ppc64@0.27.7': + optional: true + '@esbuild/android-arm64@0.27.1': optional: true + '@esbuild/android-arm64@0.27.7': + optional: true + '@esbuild/android-arm@0.27.1': optional: true + '@esbuild/android-arm@0.27.7': + optional: true + '@esbuild/android-x64@0.27.1': optional: true + '@esbuild/android-x64@0.27.7': + optional: true + '@esbuild/darwin-arm64@0.27.1': optional: true + '@esbuild/darwin-arm64@0.27.7': + optional: true + '@esbuild/darwin-x64@0.27.1': optional: true + '@esbuild/darwin-x64@0.27.7': + optional: true + '@esbuild/freebsd-arm64@0.27.1': optional: true + '@esbuild/freebsd-arm64@0.27.7': + optional: true + '@esbuild/freebsd-x64@0.27.1': optional: true + '@esbuild/freebsd-x64@0.27.7': + optional: true + '@esbuild/linux-arm64@0.27.1': optional: true + '@esbuild/linux-arm64@0.27.7': + optional: true + '@esbuild/linux-arm@0.27.1': optional: true + '@esbuild/linux-arm@0.27.7': + optional: true + '@esbuild/linux-ia32@0.27.1': optional: true + '@esbuild/linux-ia32@0.27.7': + optional: true + '@esbuild/linux-loong64@0.27.1': optional: true + '@esbuild/linux-loong64@0.27.7': + optional: true + '@esbuild/linux-mips64el@0.27.1': optional: true + '@esbuild/linux-mips64el@0.27.7': + optional: true + '@esbuild/linux-ppc64@0.27.1': optional: true + '@esbuild/linux-ppc64@0.27.7': + optional: true + '@esbuild/linux-riscv64@0.27.1': optional: true + '@esbuild/linux-riscv64@0.27.7': + optional: true + '@esbuild/linux-s390x@0.27.1': optional: true + '@esbuild/linux-s390x@0.27.7': + optional: true + '@esbuild/linux-x64@0.27.1': optional: true + '@esbuild/linux-x64@0.27.7': + optional: true + '@esbuild/netbsd-arm64@0.27.1': optional: true + '@esbuild/netbsd-arm64@0.27.7': + optional: true + '@esbuild/netbsd-x64@0.27.1': optional: true + '@esbuild/netbsd-x64@0.27.7': + optional: true + '@esbuild/openbsd-arm64@0.27.1': optional: true + '@esbuild/openbsd-arm64@0.27.7': + optional: true + '@esbuild/openbsd-x64@0.27.1': optional: true + '@esbuild/openbsd-x64@0.27.7': + optional: true + '@esbuild/openharmony-arm64@0.27.1': optional: true + '@esbuild/openharmony-arm64@0.27.7': + optional: true + '@esbuild/sunos-x64@0.27.1': optional: true + '@esbuild/sunos-x64@0.27.7': + optional: true + '@esbuild/win32-arm64@0.27.1': optional: true + '@esbuild/win32-arm64@0.27.7': + optional: true + '@esbuild/win32-ia32@0.27.1': optional: true + '@esbuild/win32-ia32@0.27.7': + optional: true + '@esbuild/win32-x64@0.27.1': optional: true + '@esbuild/win32-x64@0.27.7': + optional: true + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.2(jiti@2.6.1))': dependencies: eslint: 9.39.2(jiti@2.6.1) eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))': + dependencies: + eslint: 9.39.2(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + '@eslint-community/regexpp@4.12.2': {} - '@eslint/compat@2.0.0(eslint@9.39.2(jiti@2.6.1))': + '@eslint/compat@2.0.4(eslint@9.39.2(jiti@2.6.1))': dependencies: - '@eslint/core': 1.0.0 + '@eslint/core': 1.2.0 optionalDependencies: eslint: 9.39.2(jiti@2.6.1) @@ -13291,7 +15419,7 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/core@1.0.0': + '@eslint/core@1.2.0': dependencies: '@types/json-schema': 7.0.15 @@ -13309,6 +15437,20 @@ snapshots: transitivePeerDependencies: - supports-color + '@eslint/eslintrc@3.3.5': + dependencies: + ajv: 6.14.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + '@eslint/js@9.39.2': {} '@eslint/object-schema@2.1.7': {} @@ -13320,74 +15462,41 @@ snapshots: '@exodus/schemasafe@1.3.0': {} - '@faker-js/faker@10.2.0': {} + '@faker-js/faker@10.4.0': {} '@faker-js/faker@5.5.3': {} - '@formatjs/ecma402-abstract@2.3.6': - dependencies: - '@formatjs/fast-memoize': 2.2.7 - '@formatjs/intl-localematcher': 0.6.2 - decimal.js: 10.6.0 - tslib: 2.8.1 + '@fastify/busboy@2.1.1': {} - '@formatjs/fast-memoize@2.2.7': - dependencies: - tslib: 2.8.1 + '@formatjs/bigdecimal@0.2.0': {} - '@formatjs/icu-messageformat-parser@2.11.4': + '@formatjs/ecma402-abstract@3.2.0': dependencies: - '@formatjs/ecma402-abstract': 2.3.6 - '@formatjs/icu-skeleton-parser': 1.8.16 - tslib: 2.8.1 + '@formatjs/bigdecimal': 0.2.0 + '@formatjs/fast-memoize': 3.1.1 + '@formatjs/intl-localematcher': 0.8.2 - '@formatjs/icu-skeleton-parser@1.8.16': - dependencies: - '@formatjs/ecma402-abstract': 2.3.6 - tslib: 2.8.1 + '@formatjs/fast-memoize@3.1.1': {} - '@formatjs/intl-localematcher@0.6.2': + '@formatjs/icu-messageformat-parser@3.5.3': dependencies: - tslib: 2.8.1 + '@formatjs/ecma402-abstract': 3.2.0 + '@formatjs/icu-skeleton-parser': 2.1.3 - '@formatjs/ts-transformer@3.14.2': + '@formatjs/icu-skeleton-parser@2.1.3': dependencies: - '@formatjs/icu-messageformat-parser': 2.11.4 - '@types/node': 22.19.3 - chalk: 4.1.2 - json-stable-stringify: 1.3.0 - tslib: 2.8.1 - typescript: 5.9.3 + '@formatjs/ecma402-abstract': 3.2.0 - '@google-cloud/paginator@5.0.2': + '@formatjs/intl-localematcher@0.8.2': dependencies: - arrify: 2.0.1 - extend: 3.0.2 - - '@google-cloud/projectify@4.0.0': {} + '@formatjs/fast-memoize': 3.1.1 - '@google-cloud/promisify@4.0.0': {} - - '@google-cloud/storage@7.18.0(encoding@0.1.13)': + '@formatjs/ts-transformer@4.4.3': dependencies: - '@google-cloud/paginator': 5.0.2 - '@google-cloud/projectify': 4.0.0 - '@google-cloud/promisify': 4.0.0 - abort-controller: 3.0.0 - async-retry: 1.3.3 - duplexify: 4.1.3 - fast-xml-parser: 4.5.3 - gaxios: 6.7.1(encoding@0.1.13) - google-auth-library: 9.15.1(encoding@0.1.13) - html-entities: 2.6.0 - mime: 3.0.0 - p-limit: 3.1.0 - retry-request: 7.0.2(encoding@0.1.13) - teeny-request: 9.0.0(encoding@0.1.13) - uuid: 8.3.2 - transitivePeerDependencies: - - encoding - - supports-color + '@formatjs/icu-messageformat-parser': 3.5.3 + '@types/node': 22.19.3 + json-stable-stringify: 1.3.0 + typescript: 6.0.2 '@grpc/grpc-js@1.14.3': dependencies: @@ -13416,11 +15525,17 @@ snapshots: dependencies: '@hapi/hoek': 9.3.0 - '@hookform/error-message@2.0.1(react-dom@19.2.3(react@19.2.3))(react-hook-form@7.70.0(react@19.2.3))(react@19.2.3)': + '@hono/node-server@1.19.13(hono@4.12.12)': + dependencies: + hono: 4.12.12 + + '@hookform/error-message@2.0.1(react-dom@19.2.5(react@19.2.5))(react-hook-form@7.70.0(react@19.2.5))(react@19.2.5)': dependencies: - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - react-hook-form: 7.70.0(react@19.2.3) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + react-hook-form: 7.70.0(react@19.2.5) + + '@httptoolkit/esm@3.3.2': {} '@humanfs/core@0.19.1': {} @@ -13433,51 +15548,48 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@inquirer/ansi@1.0.2': {} + '@inquirer/ansi@2.0.5': {} - '@inquirer/core@10.3.2(@types/node@25.0.3)': + '@inquirer/core@11.1.8(@types/node@25.5.2)': dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.0.3) + '@inquirer/ansi': 2.0.5 + '@inquirer/figures': 2.0.5 + '@inquirer/type': 4.0.5(@types/node@25.5.2) cli-width: 4.1.0 - mute-stream: 2.0.0 + fast-wrap-ansi: 0.2.0 + mute-stream: 3.0.0 signal-exit: 4.1.0 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 - '@inquirer/expand@4.0.23(@types/node@25.0.3)': + '@inquirer/expand@5.0.11(@types/node@25.5.2)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.0.3) - '@inquirer/type': 3.0.10(@types/node@25.0.3) - yoctocolors-cjs: 2.1.3 + '@inquirer/core': 11.1.8(@types/node@25.5.2) + '@inquirer/type': 4.0.5(@types/node@25.5.2) optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 - '@inquirer/figures@1.0.15': {} + '@inquirer/figures@2.0.5': {} - '@inquirer/input@4.3.1(@types/node@25.0.3)': + '@inquirer/input@5.0.11(@types/node@25.5.2)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.0.3) - '@inquirer/type': 3.0.10(@types/node@25.0.3) + '@inquirer/core': 11.1.8(@types/node@25.5.2) + '@inquirer/type': 4.0.5(@types/node@25.5.2) optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 - '@inquirer/select@4.4.2(@types/node@25.0.3)': + '@inquirer/select@5.1.3(@types/node@25.5.2)': dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.0.3) - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.0.3) - yoctocolors-cjs: 2.1.3 + '@inquirer/ansi': 2.0.5 + '@inquirer/core': 11.1.8(@types/node@25.5.2) + '@inquirer/figures': 2.0.5 + '@inquirer/type': 4.0.5(@types/node@25.5.2) optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 - '@inquirer/type@3.0.10(@types/node@25.0.3)': + '@inquirer/type@4.0.5(@types/node@25.5.2)': optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@isaacs/balanced-match@4.0.1': {} @@ -13494,6 +15606,8 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/cliui@9.0.0': {} + '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.2 @@ -13508,44 +15622,43 @@ snapshots: '@istanbuljs/schema@0.1.3': {} - '@jest/console@30.2.0': + '@jest/console@30.3.0': dependencies: - '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@jest/types': 30.3.0 + '@types/node': 25.5.2 chalk: 4.1.2 - jest-message-util: 30.2.0 - jest-util: 30.2.0 + jest-message-util: 30.3.0 + jest-util: 30.3.0 slash: 3.0.0 - '@jest/core@30.2.0(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))': + '@jest/core@30.3.0(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2))': dependencies: - '@jest/console': 30.2.0 + '@jest/console': 30.3.0 '@jest/pattern': 30.0.1 - '@jest/reporters': 30.2.0 - '@jest/test-result': 30.2.0 - '@jest/transform': 30.2.0 - '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@jest/reporters': 30.3.0 + '@jest/test-result': 30.3.0 + '@jest/transform': 30.3.0 + '@jest/types': 30.3.0 + '@types/node': 25.5.2 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 4.3.1 exit-x: 0.2.2 graceful-fs: 4.2.11 - jest-changed-files: 30.2.0 - jest-config: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) - jest-haste-map: 30.2.0 - jest-message-util: 30.2.0 + jest-changed-files: 30.3.0 + jest-config: 30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2)) + jest-haste-map: 30.3.0 + jest-message-util: 30.3.0 jest-regex-util: 30.0.1 - jest-resolve: 30.2.0 - jest-resolve-dependencies: 30.2.0 - jest-runner: 30.2.0 - jest-runtime: 30.2.0 - jest-snapshot: 30.2.0 - jest-util: 30.2.0 - jest-validate: 30.2.0 - jest-watcher: 30.2.0 - micromatch: 4.0.8 - pretty-format: 30.2.0 + jest-resolve: 30.3.0 + jest-resolve-dependencies: 30.3.0 + jest-runner: 30.3.0 + jest-runtime: 30.3.0 + jest-snapshot: 30.3.0 + jest-util: 30.3.0 + jest-validate: 30.3.0 + jest-watcher: 30.3.0 + pretty-format: 30.3.0 slash: 3.0.0 transitivePeerDependencies: - babel-plugin-macros @@ -13555,13 +15668,15 @@ snapshots: '@jest/diff-sequences@30.0.1': {} + '@jest/diff-sequences@30.3.0': {} + '@jest/environment-jsdom-abstract@30.2.0(jsdom@26.1.0)': dependencies: '@jest/environment': 30.2.0 '@jest/fake-timers': 30.2.0 '@jest/types': 30.2.0 '@types/jsdom': 21.1.7 - '@types/node': 25.0.3 + '@types/node': 25.5.2 jest-mock: 30.2.0 jest-util: 30.2.0 jsdom: 26.1.0 @@ -13570,17 +15685,28 @@ snapshots: dependencies: '@jest/fake-timers': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 25.5.2 jest-mock: 30.2.0 + '@jest/environment@30.3.0': + dependencies: + '@jest/fake-timers': 30.3.0 + '@jest/types': 30.3.0 + '@types/node': 25.5.2 + jest-mock: 30.3.0 + '@jest/expect-utils@30.2.0': dependencies: '@jest/get-type': 30.1.0 - '@jest/expect@30.2.0': + '@jest/expect-utils@30.3.0': dependencies: - expect: 30.2.0 - jest-snapshot: 30.2.0 + '@jest/get-type': 30.1.0 + + '@jest/expect@30.3.0': + dependencies: + expect: 30.3.0 + jest-snapshot: 30.3.0 transitivePeerDependencies: - supports-color @@ -13588,36 +15714,45 @@ snapshots: dependencies: '@jest/types': 30.2.0 '@sinonjs/fake-timers': 13.0.5 - '@types/node': 25.0.3 + '@types/node': 25.5.2 jest-message-util: 30.2.0 jest-mock: 30.2.0 jest-util: 30.2.0 + '@jest/fake-timers@30.3.0': + dependencies: + '@jest/types': 30.3.0 + '@sinonjs/fake-timers': 15.3.0 + '@types/node': 25.5.2 + jest-message-util: 30.3.0 + jest-mock: 30.3.0 + jest-util: 30.3.0 + '@jest/get-type@30.1.0': {} - '@jest/globals@30.2.0': + '@jest/globals@30.3.0': dependencies: - '@jest/environment': 30.2.0 - '@jest/expect': 30.2.0 - '@jest/types': 30.2.0 - jest-mock: 30.2.0 + '@jest/environment': 30.3.0 + '@jest/expect': 30.3.0 + '@jest/types': 30.3.0 + jest-mock: 30.3.0 transitivePeerDependencies: - supports-color '@jest/pattern@30.0.1': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 jest-regex-util: 30.0.1 - '@jest/reporters@30.2.0': + '@jest/reporters@30.3.0': dependencies: '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 30.2.0 - '@jest/test-result': 30.2.0 - '@jest/transform': 30.2.0 - '@jest/types': 30.2.0 + '@jest/console': 30.3.0 + '@jest/test-result': 30.3.0 + '@jest/transform': 30.3.0 + '@jest/types': 30.3.0 '@jridgewell/trace-mapping': 0.3.31 - '@types/node': 25.0.3 + '@types/node': 25.5.2 chalk: 4.1.2 collect-v8-coverage: 1.0.3 exit-x: 0.2.2 @@ -13628,9 +15763,9 @@ snapshots: istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 istanbul-reports: 3.2.0 - jest-message-util: 30.2.0 - jest-util: 30.2.0 - jest-worker: 30.2.0 + jest-message-util: 30.3.0 + jest-util: 30.3.0 + jest-worker: 30.3.0 slash: 3.0.0 string-length: 4.0.2 v8-to-istanbul: 9.3.0 @@ -13645,9 +15780,9 @@ snapshots: dependencies: '@sinclair/typebox': 0.34.45 - '@jest/snapshot-utils@30.2.0': + '@jest/snapshot-utils@30.3.0': dependencies: - '@jest/types': 30.2.0 + '@jest/types': 30.3.0 chalk: 4.1.2 graceful-fs: 4.2.11 natural-compare: 1.4.0 @@ -13658,34 +15793,33 @@ snapshots: callsites: 3.1.0 graceful-fs: 4.2.11 - '@jest/test-result@30.2.0': + '@jest/test-result@30.3.0': dependencies: - '@jest/console': 30.2.0 - '@jest/types': 30.2.0 + '@jest/console': 30.3.0 + '@jest/types': 30.3.0 '@types/istanbul-lib-coverage': 2.0.6 collect-v8-coverage: 1.0.3 - '@jest/test-sequencer@30.2.0': + '@jest/test-sequencer@30.3.0': dependencies: - '@jest/test-result': 30.2.0 + '@jest/test-result': 30.3.0 graceful-fs: 4.2.11 - jest-haste-map: 30.2.0 + jest-haste-map: 30.3.0 slash: 3.0.0 - '@jest/transform@30.2.0': + '@jest/transform@30.3.0': dependencies: '@babel/core': 7.28.5 - '@jest/types': 30.2.0 + '@jest/types': 30.3.0 '@jridgewell/trace-mapping': 0.3.31 babel-plugin-istanbul: 7.0.1 chalk: 4.1.2 convert-source-map: 2.0.0 fast-json-stable-stringify: 2.1.0 graceful-fs: 4.2.11 - jest-haste-map: 30.2.0 + jest-haste-map: 30.3.0 jest-regex-util: 30.0.1 - jest-util: 30.2.0 - micromatch: 4.0.8 + jest-util: 30.3.0 pirates: 4.0.7 slash: 3.0.0 write-file-atomic: 5.0.1 @@ -13697,7 +15831,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -13707,7 +15841,17 @@ snapshots: '@jest/schemas': 30.0.5 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 25.0.3 + '@types/node': 25.5.2 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + + '@jest/types@30.3.0': + dependencies: + '@jest/pattern': 30.0.1 + '@jest/schemas': 30.0.5 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 25.5.2 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -13739,12 +15883,9 @@ snapshots: dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - optional: true '@js-sdsl/ordered-map@4.4.2': {} - '@jsdevtools/ono@7.1.3': {} - '@jsep-plugin/assignment@1.3.0(jsep@1.4.0)': dependencies: jsep: 1.4.0 @@ -13820,14 +15961,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@kwsites/file-exists@1.1.1': + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@kwsites/promise-deferred@1.1.1': {} + '@leichtgewicht/ip-codec@2.0.5': {} - '@lerna-lite/changed@4.10.2(@lerna-lite/version@4.10.2)(@types/node@25.0.3)': + '@lerna-lite/changed@5.0.0(@lerna-lite/version@5.0.0)(@types/node@25.5.2)': dependencies: - '@lerna-lite/cli': 4.10.2(@lerna-lite/list@4.10.2)(@lerna-lite/version@4.10.2)(@types/node@25.0.3) - '@lerna-lite/core': 4.10.2(@types/node@25.0.3) - '@lerna-lite/list': 4.10.2(@lerna-lite/version@4.10.2)(@types/node@25.0.3) - '@lerna-lite/listable': 4.10.2(@types/node@25.0.3) + '@lerna-lite/cli': 5.0.0(@lerna-lite/list@5.0.0)(@lerna-lite/version@5.0.0)(@types/node@25.5.2) + '@lerna-lite/core': 5.0.0(@types/node@25.5.2) + '@lerna-lite/list': 5.0.0(@lerna-lite/version@5.0.0)(@types/node@25.5.2) + '@lerna-lite/listable': 5.0.0(@types/node@25.5.2) transitivePeerDependencies: - '@lerna-lite/exec' - '@lerna-lite/publish' @@ -13838,61 +15987,56 @@ snapshots: - babel-plugin-macros - supports-color - '@lerna-lite/cli@4.10.2(@lerna-lite/list@4.10.2)(@lerna-lite/version@4.10.2)(@types/node@25.0.3)': + '@lerna-lite/cli@5.0.0(@lerna-lite/list@5.0.0)(@lerna-lite/version@5.0.0)(@types/node@25.5.2)': dependencies: - '@lerna-lite/core': 4.10.2(@types/node@25.0.3) - '@lerna-lite/init': 4.10.2(@types/node@25.0.3) - '@lerna-lite/npmlog': 4.10.0 - dedent: 1.7.0 - dotenv: 17.2.3 + '@lerna-lite/core': 5.0.0(@types/node@25.5.2) + '@lerna-lite/init': 5.0.0(@types/node@25.5.2) + '@lerna-lite/npmlog': 5.0.0 + dedent: 1.7.2 + dotenv: 17.4.1 import-local: 3.2.0 load-json-file: 7.0.1 yargs: 18.0.0 optionalDependencies: - '@lerna-lite/list': 4.10.2(@lerna-lite/version@4.10.2)(@types/node@25.0.3) - '@lerna-lite/version': 4.10.2(@lerna-lite/list@4.10.2)(@types/node@25.0.3)(conventional-commits-filter@5.0.0) + '@lerna-lite/list': 5.0.0(@lerna-lite/version@5.0.0)(@types/node@25.5.2) + '@lerna-lite/version': 5.0.0(@lerna-lite/list@5.0.0)(@types/node@25.5.2)(conventional-commits-filter@5.0.0) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - '@lerna-lite/core@4.10.2(@types/node@25.0.3)': + '@lerna-lite/core@5.0.0(@types/node@25.5.2)': dependencies: - '@inquirer/expand': 4.0.23(@types/node@25.0.3) - '@inquirer/input': 4.3.1(@types/node@25.0.3) - '@inquirer/select': 4.4.2(@types/node@25.0.3) - '@lerna-lite/npmlog': 4.10.0 - '@npmcli/run-script': 10.0.3 - ci-info: 4.3.1 - config-chain: 1.1.13 - dedent: 1.7.0 + '@inquirer/expand': 5.0.11(@types/node@25.5.2) + '@inquirer/input': 5.0.11(@types/node@25.5.2) + '@inquirer/select': 5.1.3(@types/node@25.5.2) + '@lerna-lite/npmlog': 5.0.0 + '@npmcli/run-script': 10.0.4 + ci-info: 4.4.0 + dedent: 1.7.2 execa: 9.6.1 - fs-extra: 11.3.2 - glob-parent: 6.0.2 + fs-extra: 11.3.4 json5: 2.2.3 lilconfig: 3.1.3 load-json-file: 7.0.1 npm-package-arg: 13.0.2 p-map: 7.0.4 - p-queue: 9.0.1 - semver: 7.7.3 - slash: 5.1.0 + p-queue: 9.1.2 + semver: 7.7.4 tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 - write-file-atomic: 7.0.0 + write-file-atomic: 7.0.1 write-json-file: 7.0.0 - write-package: 7.2.0 - yaml: 2.8.2 + yaml: 2.8.3 zeptomatch: 2.1.0 transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - '@lerna-lite/init@4.10.2(@types/node@25.0.3)': + '@lerna-lite/init@5.0.0(@types/node@25.5.2)': dependencies: - '@lerna-lite/core': 4.10.2(@types/node@25.0.3) - fs-extra: 11.3.2 + '@lerna-lite/core': 5.0.0(@types/node@25.5.2) + fs-extra: 11.3.4 p-map: 7.0.4 write-json-file: 7.0.0 transitivePeerDependencies: @@ -13900,11 +16044,11 @@ snapshots: - babel-plugin-macros - supports-color - '@lerna-lite/list@4.10.2(@lerna-lite/version@4.10.2)(@types/node@25.0.3)': + '@lerna-lite/list@5.0.0(@lerna-lite/version@5.0.0)(@types/node@25.5.2)': dependencies: - '@lerna-lite/cli': 4.10.2(@lerna-lite/list@4.10.2)(@lerna-lite/version@4.10.2)(@types/node@25.0.3) - '@lerna-lite/core': 4.10.2(@types/node@25.0.3) - '@lerna-lite/listable': 4.10.2(@types/node@25.0.3) + '@lerna-lite/cli': 5.0.0(@lerna-lite/list@5.0.0)(@lerna-lite/version@5.0.0)(@types/node@25.5.2) + '@lerna-lite/core': 5.0.0(@types/node@25.5.2) + '@lerna-lite/listable': 5.0.0(@types/node@25.5.2) transitivePeerDependencies: - '@lerna-lite/exec' - '@lerna-lite/publish' @@ -13915,55 +16059,42 @@ snapshots: - babel-plugin-macros - supports-color - '@lerna-lite/listable@4.10.2(@types/node@25.0.3)': + '@lerna-lite/listable@5.0.0(@types/node@25.5.2)': dependencies: - '@lerna-lite/core': 4.10.2(@types/node@25.0.3) + '@lerna-lite/core': 5.0.0(@types/node@25.5.2) columnify: 1.6.0 - tinyrainbow: 3.0.3 transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - '@lerna-lite/npmlog@4.10.0': + '@lerna-lite/npmlog@5.0.0': dependencies: - aproba: 2.1.0 fast-string-width: 3.0.2 - has-unicode: 2.0.1 - set-blocking: 2.0.0 signal-exit: 4.1.0 - tinyrainbow: 3.0.3 - wide-align: 1.1.5 - '@lerna-lite/version@4.10.2(@lerna-lite/list@4.10.2)(@types/node@25.0.3)(conventional-commits-filter@5.0.0)': + '@lerna-lite/version@5.0.0(@lerna-lite/list@5.0.0)(@types/node@25.5.2)(conventional-commits-filter@5.0.0)': dependencies: - '@conventional-changelog/git-client': 2.5.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.2.1) - '@lerna-lite/cli': 4.10.2(@lerna-lite/list@4.10.2)(@lerna-lite/version@4.10.2)(@types/node@25.0.3) - '@lerna-lite/core': 4.10.2(@types/node@25.0.3) - '@lerna-lite/npmlog': 4.10.0 + '@conventional-changelog/git-client': 2.6.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0) + '@lerna-lite/cli': 5.0.0(@lerna-lite/list@5.0.0)(@lerna-lite/version@5.0.0)(@types/node@25.5.2) + '@lerna-lite/core': 5.0.0(@types/node@25.5.2) + '@lerna-lite/npmlog': 5.0.0 '@octokit/plugin-enterprise-rest': 6.0.1 '@octokit/rest': 22.0.1 - conventional-changelog: 7.1.1(conventional-commits-filter@5.0.0) - conventional-changelog-angular: 8.1.0 - conventional-changelog-writer: 8.2.0 - conventional-commits-parser: 6.2.1 + conventional-changelog: 7.2.0(conventional-commits-filter@5.0.0) + conventional-changelog-angular: 8.3.1 + conventional-changelog-writer: 8.4.0 + conventional-commits-parser: 6.4.0 conventional-recommended-bump: 11.2.0 - dedent: 1.7.0 - fs-extra: 11.3.2 + dedent: 1.7.2 + fs-extra: 11.3.4 git-url-parse: 16.1.0 - is-stream: 4.0.1 load-json-file: 7.0.1 new-github-release-url: 2.0.0 npm-package-arg: 13.0.2 - p-limit: 7.2.0 + p-limit: 7.3.0 p-map: 7.0.4 - p-pipe: 4.0.0 - p-reduce: 3.0.0 - pify: 6.1.0 - semver: 7.7.3 - slash: 5.1.0 - tinyrainbow: 3.0.3 - uuid: 13.0.0 + semver: 7.7.4 write-json-file: 7.0.0 zeptomatch: 2.1.0 transitivePeerDependencies: @@ -13977,6 +16108,19 @@ snapshots: - conventional-commits-filter - supports-color + '@mapbox/node-pre-gyp@2.0.3(encoding@0.1.13)': + dependencies: + consola: 3.4.2 + detect-libc: 2.1.2 + https-proxy-agent: 7.0.6 + node-fetch: 2.7.0(encoding@0.1.13) + nopt: 8.1.0 + semver: 7.7.4 + tar: 7.5.2 + transitivePeerDependencies: + - encoding + - supports-color + '@mdx-js/mdx@3.1.1': dependencies: '@types/estree': 1.0.8 @@ -14007,14 +16151,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3)': + '@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5)': dependencies: '@types/mdx': 2.0.13 '@types/react': 19.2.7 - react: 19.2.3 + react: 19.2.5 - '@modelcontextprotocol/sdk@1.24.3(zod@3.25.76)': + '@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)': dependencies: + '@hono/node-server': 1.19.13(hono@4.12.12) ajv: 8.17.1 ajv-formats: 3.0.1(ajv@8.17.1) content-type: 1.0.5 @@ -14023,15 +16168,42 @@ snapshots: eventsource: 3.0.7 eventsource-parser: 3.0.6 express: 5.2.1 - express-rate-limit: 7.5.1(express@5.2.1) + express-rate-limit: 8.3.2(express@5.2.1) + hono: 4.12.12 jose: 6.1.3 + json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 raw-body: 3.0.2 zod: 3.25.76 - zod-to-json-schema: 3.25.0(zod@3.25.76) + zod-to-json-schema: 3.25.2(zod@3.25.76) transitivePeerDependencies: - supports-color + '@module-federation/error-codes@0.22.0': {} + + '@module-federation/runtime-core@0.22.0': + dependencies: + '@module-federation/error-codes': 0.22.0 + '@module-federation/sdk': 0.22.0 + + '@module-federation/runtime-tools@0.22.0': + dependencies: + '@module-federation/runtime': 0.22.0 + '@module-federation/webpack-bundler-runtime': 0.22.0 + + '@module-federation/runtime@0.22.0': + dependencies: + '@module-federation/error-codes': 0.22.0 + '@module-federation/runtime-core': 0.22.0 + '@module-federation/sdk': 0.22.0 + + '@module-federation/sdk@0.22.0': {} + + '@module-federation/webpack-bundler-runtime@0.22.0': + dependencies: + '@module-federation/runtime': 0.22.0 + '@module-federation/sdk': 0.22.0 + '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.7.1 @@ -14039,6 +16211,13 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true + '@napi-rs/wasm-runtime@1.0.7': + dependencies: + '@emnapi/core': 1.7.1 + '@emnapi/runtime': 1.7.1 + '@tybys/wasm-util': 0.10.1 + optional: true + '@noble/hashes@1.8.0': {} '@nodelib/fs.scandir@2.1.5': @@ -14065,7 +16244,7 @@ snapshots: '@npmcli/fs@5.0.0': dependencies: - semver: 7.7.3 + semver: 7.7.4 '@npmcli/git@7.0.1': dependencies: @@ -14075,7 +16254,7 @@ snapshots: npm-pick-manifest: 11.0.3 proc-log: 6.1.0 promise-retry: 2.0.1 - semver: 7.7.3 + semver: 7.7.4 which: 6.0.0 '@npmcli/node-gyp@5.0.0': {} @@ -14087,21 +16266,20 @@ snapshots: hosted-git-info: 9.0.2 json-parse-even-better-errors: 5.0.0 proc-log: 6.1.0 - semver: 7.7.3 + semver: 7.7.4 validate-npm-package-license: 3.0.4 '@npmcli/promise-spawn@9.0.1': dependencies: which: 6.0.0 - '@npmcli/run-script@10.0.3': + '@npmcli/run-script@10.0.4': dependencies: '@npmcli/node-gyp': 5.0.0 '@npmcli/package-json': 7.0.4 '@npmcli/promise-spawn': 9.0.1 node-gyp: 12.1.0 proc-log: 6.1.0 - which: 6.0.0 transitivePeerDependencies: - supports-color @@ -14128,6 +16306,8 @@ snapshots: '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 + '@octokit/openapi-types@24.2.0': {} + '@octokit/openapi-types@27.0.0': {} '@octokit/plugin-enterprise-rest@6.0.1': {} @@ -14146,6 +16326,12 @@ snapshots: '@octokit/core': 7.0.6 '@octokit/types': 16.0.0 + '@octokit/request-error@5.1.1': + dependencies: + '@octokit/types': 13.10.0 + deprecation: 2.3.1 + once: 1.4.0 + '@octokit/request-error@7.1.0': dependencies: '@octokit/types': 16.0.0 @@ -14165,10 +16351,25 @@ snapshots: '@octokit/plugin-request-log': 6.0.0(@octokit/core@7.0.6) '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.6) + '@octokit/types@13.10.0': + dependencies: + '@octokit/openapi-types': 24.2.0 + '@octokit/types@16.0.0': dependencies: '@octokit/openapi-types': 27.0.0 + '@octokit/webhooks-methods@4.1.0': {} + + '@octokit/webhooks-types@7.6.1': {} + + '@octokit/webhooks@12.3.2': + dependencies: + '@octokit/request-error': 5.1.1 + '@octokit/webhooks-methods': 4.1.0 + '@octokit/webhooks-types': 7.6.1 + aggregate-error: 3.1.0 + '@opentelemetry/api@1.9.0': {} '@paralleldrive/cuid2@2.3.1': @@ -14276,184 +16477,610 @@ snapshots: '@protobufjs/pool@1.1.0': {} - '@protobufjs/utf8@1.1.0': {} + '@protobufjs/utf8@1.1.0': {} + + '@redocly/ajv@8.18.0': + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + '@redocly/ajv@8.18.3': + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + '@redocly/config@0.46.1': + dependencies: + json-schema-to-ts: 2.7.2 + + '@redocly/openapi-core@2.26.0': + dependencies: + '@redocly/ajv': 8.18.3 + '@redocly/config': 0.46.1 + ajv: '@redocly/ajv@8.18.0' + ajv-formats: 3.0.1(@redocly/ajv@8.18.0) + colorette: 1.4.0 + js-levenshtein: 1.1.6 + js-yaml: 4.1.1 + picomatch: 4.0.4 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + + '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.7)(react@19.2.5)(redux@5.0.1))(react@19.2.5)': + dependencies: + '@standard-schema/spec': 1.0.0 + '@standard-schema/utils': 0.3.0 + immer: 11.1.4 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.1.1 + optionalDependencies: + react: 19.2.5 + react-redux: 9.2.0(@types/react@19.2.7)(react@19.2.5)(redux@5.0.1) + + '@rollup/plugin-commonjs@22.0.2(rollup@2.79.2)': + dependencies: + '@rollup/pluginutils': 3.1.0(rollup@2.79.2) + commondir: 1.0.1 + estree-walker: 2.0.2 + glob: 7.2.3 + is-reference: 1.2.1 + magic-string: 0.25.9 + resolve: 1.22.11 + rollup: 2.79.2 + + '@rollup/pluginutils@3.1.0(rollup@2.79.2)': + dependencies: + '@types/estree': 0.0.39 + estree-walker: 1.0.1 + picomatch: 2.3.1 + rollup: 2.79.2 + + '@rollup/pluginutils@5.3.0(rollup@4.53.3)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.4 + optionalDependencies: + rollup: 4.53.3 + + '@rollup/rollup-android-arm-eabi@4.53.3': + optional: true + + '@rollup/rollup-android-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-x64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-arm64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-x64@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-musl@4.53.3': + optional: true + + '@rollup/rollup-openharmony-arm64@4.53.3': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.53.3': + optional: true + + '@rspack/binding-darwin-arm64@1.7.11': + optional: true + + '@rspack/binding-darwin-x64@1.7.11': + optional: true + + '@rspack/binding-linux-arm64-gnu@1.7.11': + optional: true + + '@rspack/binding-linux-arm64-musl@1.7.11': + optional: true + + '@rspack/binding-linux-x64-gnu@1.7.11': + optional: true + + '@rspack/binding-linux-x64-musl@1.7.11': + optional: true + + '@rspack/binding-wasm32-wasi@1.7.11': + dependencies: + '@napi-rs/wasm-runtime': 1.0.7 + optional: true + + '@rspack/binding-win32-arm64-msvc@1.7.11': + optional: true + + '@rspack/binding-win32-ia32-msvc@1.7.11': + optional: true + + '@rspack/binding-win32-x64-msvc@1.7.11': + optional: true + + '@rspack/binding@1.7.11': + optionalDependencies: + '@rspack/binding-darwin-arm64': 1.7.11 + '@rspack/binding-darwin-x64': 1.7.11 + '@rspack/binding-linux-arm64-gnu': 1.7.11 + '@rspack/binding-linux-arm64-musl': 1.7.11 + '@rspack/binding-linux-x64-gnu': 1.7.11 + '@rspack/binding-linux-x64-musl': 1.7.11 + '@rspack/binding-wasm32-wasi': 1.7.11 + '@rspack/binding-win32-arm64-msvc': 1.7.11 + '@rspack/binding-win32-ia32-msvc': 1.7.11 + '@rspack/binding-win32-x64-msvc': 1.7.11 + + '@rspack/core@1.7.11': + dependencies: + '@module-federation/runtime-tools': 0.22.0 + '@rspack/binding': 1.7.11 + '@rspack/lite-tapable': 1.1.0 + + '@rspack/lite-tapable@1.1.0': {} + + '@rtsao/scc@1.1.0': {} + + '@sec-ant/readable-stream@0.4.1': {} + + '@sideway/address@4.1.5': + dependencies: + '@hapi/hoek': 9.3.0 + + '@sideway/formula@3.0.1': {} + + '@sideway/pinpoint@2.0.0': {} + + '@simple-git/args-pathspec@1.0.3': {} + + '@simple-git/argv-parser@1.1.0': + dependencies: + '@simple-git/args-pathspec': 1.0.3 + + '@simple-libs/child-process-utils@1.0.1': + dependencies: + '@simple-libs/stream-utils': 1.2.0 + '@types/node': 22.19.3 + + '@simple-libs/hosted-git-info@1.0.2': {} + + '@simple-libs/stream-utils@1.2.0': {} + + '@sinclair/typebox@0.25.24': {} + + '@sinclair/typebox@0.27.8': {} + + '@sinclair/typebox@0.34.45': {} + + '@sindresorhus/is@4.6.0': {} + + '@sindresorhus/is@5.6.0': {} + + '@sindresorhus/merge-streams@4.0.0': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@13.0.5': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@sinonjs/fake-timers@15.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@slack/types@2.20.1': {} + + '@slack/webhook@7.0.8': + dependencies: + '@slack/types': 2.20.1 + '@types/node': 25.5.2 + axios: 1.15.0 + transitivePeerDependencies: + - debug + + '@slorber/react-helmet-async@1.3.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@babel/runtime': 7.28.4 + invariant: 2.2.4 + prop-types: 15.8.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + react-fast-compare: 3.2.2 + shallowequal: 1.1.0 + + '@slorber/remark-comment@1.0.0': + dependencies: + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + + '@smithy/chunked-blob-reader-native@4.2.3': + dependencies: + '@smithy/util-base64': 4.3.2 + tslib: 2.8.1 + + '@smithy/chunked-blob-reader@5.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/config-resolver@4.4.14': + dependencies: + '@smithy/node-config-provider': 4.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-config-provider': 4.2.2 + '@smithy/util-endpoints': 3.3.4 + '@smithy/util-middleware': 4.2.13 + tslib: 2.8.1 + + '@smithy/core@3.23.14': + dependencies: + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-stream': 4.5.22 + '@smithy/util-utf8': 4.2.2 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.2.13': + dependencies: + '@smithy/node-config-provider': 4.3.13 + '@smithy/property-provider': 4.2.13 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + tslib: 2.8.1 + + '@smithy/eventstream-codec@4.2.13': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.14.0 + '@smithy/util-hex-encoding': 4.2.2 + tslib: 2.8.1 + + '@smithy/eventstream-serde-browser@4.2.13': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 + + '@smithy/eventstream-serde-config-resolver@4.3.13': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@redocly/ajv@8.17.1': + '@smithy/eventstream-serde-node@4.2.13': dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 + '@smithy/eventstream-serde-universal': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@redocly/config@0.22.2': {} + '@smithy/eventstream-serde-universal@4.2.13': + dependencies: + '@smithy/eventstream-codec': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@redocly/openapi-core@1.34.6': + '@smithy/fetch-http-handler@5.3.16': dependencies: - '@redocly/ajv': 8.17.1 - '@redocly/config': 0.22.2 - colorette: 1.4.0 - https-proxy-agent: 7.0.6 - js-levenshtein: 1.1.6 - js-yaml: 4.1.1 - minimatch: 5.1.6 - pluralize: 8.0.0 - yaml-ast-parser: 0.0.43 - transitivePeerDependencies: - - supports-color + '@smithy/protocol-http': 5.3.13 + '@smithy/querystring-builder': 4.2.13 + '@smithy/types': 4.14.0 + '@smithy/util-base64': 4.3.2 + tslib: 2.8.1 - '@reduxjs/toolkit@1.9.7(react-redux@7.2.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)': + '@smithy/hash-blob-browser@4.2.14': dependencies: - immer: 9.0.21 - redux: 4.2.1 - redux-thunk: 2.4.2(redux@4.2.1) - reselect: 4.1.8 - optionalDependencies: - react: 19.2.3 - react-redux: 7.2.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@smithy/chunked-blob-reader': 5.2.2 + '@smithy/chunked-blob-reader-native': 4.2.3 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/plugin-commonjs@22.0.2(rollup@2.79.2)': + '@smithy/hash-node@4.2.13': dependencies: - '@rollup/pluginutils': 3.1.0(rollup@2.79.2) - commondir: 1.0.1 - estree-walker: 2.0.2 - glob: 7.2.3 - is-reference: 1.2.1 - magic-string: 0.25.9 - resolve: 1.22.11 - rollup: 2.79.2 + '@smithy/types': 4.14.0 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 - '@rollup/pluginutils@3.1.0(rollup@2.79.2)': + '@smithy/hash-stream-node@4.2.13': dependencies: - '@types/estree': 0.0.39 - estree-walker: 1.0.1 - picomatch: 2.3.1 - rollup: 2.79.2 + '@smithy/types': 4.14.0 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 - '@rollup/rollup-android-arm-eabi@4.53.3': - optional: true + '@smithy/invalid-dependency@4.2.13': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-android-arm64@4.53.3': - optional: true + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 - '@rollup/rollup-darwin-arm64@4.53.3': - optional: true + '@smithy/is-array-buffer@4.2.2': + dependencies: + tslib: 2.8.1 - '@rollup/rollup-darwin-x64@4.53.3': - optional: true + '@smithy/md5-js@4.2.13': + dependencies: + '@smithy/types': 4.14.0 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 - '@rollup/rollup-freebsd-arm64@4.53.3': - optional: true + '@smithy/middleware-content-length@4.2.13': + dependencies: + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-freebsd-x64@4.53.3': - optional: true + '@smithy/middleware-endpoint@4.4.29': + dependencies: + '@smithy/core': 3.23.14 + '@smithy/middleware-serde': 4.2.17 + '@smithy/node-config-provider': 4.3.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + '@smithy/url-parser': 4.2.13 + '@smithy/util-middleware': 4.2.13 + tslib: 2.8.1 - '@rollup/rollup-linux-arm-gnueabihf@4.53.3': - optional: true + '@smithy/middleware-retry@4.5.1': + dependencies: + '@smithy/core': 3.23.14 + '@smithy/node-config-provider': 4.3.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/service-error-classification': 4.2.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-retry': 4.3.1 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 - '@rollup/rollup-linux-arm-musleabihf@4.53.3': - optional: true + '@smithy/middleware-serde@4.2.17': + dependencies: + '@smithy/core': 3.23.14 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-linux-arm64-gnu@4.53.3': - optional: true + '@smithy/middleware-stack@4.2.13': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-linux-arm64-musl@4.53.3': - optional: true + '@smithy/node-config-provider@4.3.13': + dependencies: + '@smithy/property-provider': 4.2.13 + '@smithy/shared-ini-file-loader': 4.4.8 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-linux-loong64-gnu@4.53.3': - optional: true + '@smithy/node-http-handler@4.5.2': + dependencies: + '@smithy/protocol-http': 5.3.13 + '@smithy/querystring-builder': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-linux-ppc64-gnu@4.53.3': - optional: true + '@smithy/property-provider@4.2.13': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-linux-riscv64-gnu@4.53.3': - optional: true + '@smithy/protocol-http@5.3.13': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-linux-riscv64-musl@4.53.3': - optional: true + '@smithy/querystring-builder@4.2.13': + dependencies: + '@smithy/types': 4.14.0 + '@smithy/util-uri-escape': 4.2.2 + tslib: 2.8.1 - '@rollup/rollup-linux-s390x-gnu@4.53.3': - optional: true + '@smithy/querystring-parser@4.2.13': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-linux-x64-gnu@4.53.3': - optional: true + '@smithy/service-error-classification@4.2.13': + dependencies: + '@smithy/types': 4.14.0 - '@rollup/rollup-linux-x64-musl@4.53.3': - optional: true + '@smithy/shared-ini-file-loader@4.4.8': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-openharmony-arm64@4.53.3': - optional: true + '@smithy/signature-v4@5.3.13': + dependencies: + '@smithy/is-array-buffer': 4.2.2 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-middleware': 4.2.13 + '@smithy/util-uri-escape': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 - '@rollup/rollup-win32-arm64-msvc@4.53.3': - optional: true + '@smithy/smithy-client@4.12.9': + dependencies: + '@smithy/core': 3.23.14 + '@smithy/middleware-endpoint': 4.4.29 + '@smithy/middleware-stack': 4.2.13 + '@smithy/protocol-http': 5.3.13 + '@smithy/types': 4.14.0 + '@smithy/util-stream': 4.5.22 + tslib: 2.8.1 - '@rollup/rollup-win32-ia32-msvc@4.53.3': - optional: true + '@smithy/types@4.14.0': + dependencies: + tslib: 2.8.1 - '@rollup/rollup-win32-x64-gnu@4.53.3': - optional: true + '@smithy/url-parser@4.2.13': + dependencies: + '@smithy/querystring-parser': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@rollup/rollup-win32-x64-msvc@4.53.3': - optional: true + '@smithy/util-base64@4.3.2': + dependencies: + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 - '@rtsao/scc@1.1.0': {} + '@smithy/util-body-length-browser@4.2.2': + dependencies: + tslib: 2.8.1 - '@sec-ant/readable-stream@0.4.1': {} + '@smithy/util-body-length-node@4.2.3': + dependencies: + tslib: 2.8.1 - '@sideway/address@4.1.5': + '@smithy/util-buffer-from@2.2.0': dependencies: - '@hapi/hoek': 9.3.0 + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 - '@sideway/formula@3.0.1': {} + '@smithy/util-buffer-from@4.2.2': + dependencies: + '@smithy/is-array-buffer': 4.2.2 + tslib: 2.8.1 - '@sideway/pinpoint@2.0.0': {} + '@smithy/util-config-provider@4.2.2': + dependencies: + tslib: 2.8.1 - '@simple-libs/child-process-utils@1.0.1': + '@smithy/util-defaults-mode-browser@4.3.45': dependencies: - '@simple-libs/stream-utils': 1.1.0 - '@types/node': 22.19.3 + '@smithy/property-provider': 4.2.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@simple-libs/stream-utils@1.1.0': + '@smithy/util-defaults-mode-node@4.2.49': dependencies: - '@types/node': 22.19.3 + '@smithy/config-resolver': 4.4.14 + '@smithy/credential-provider-imds': 4.2.13 + '@smithy/node-config-provider': 4.3.13 + '@smithy/property-provider': 4.2.13 + '@smithy/smithy-client': 4.12.9 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@sinclair/typebox@0.27.8': {} + '@smithy/util-endpoints@3.3.4': + dependencies: + '@smithy/node-config-provider': 4.3.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@sinclair/typebox@0.34.45': {} + '@smithy/util-hex-encoding@4.2.2': + dependencies: + tslib: 2.8.1 - '@sindresorhus/is@4.6.0': {} + '@smithy/util-middleware@4.2.13': + dependencies: + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@sindresorhus/is@5.6.0': {} + '@smithy/util-retry@4.3.1': + dependencies: + '@smithy/service-error-classification': 4.2.13 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@sindresorhus/merge-streams@2.3.0': {} + '@smithy/util-stream@4.5.22': + dependencies: + '@smithy/fetch-http-handler': 5.3.16 + '@smithy/node-http-handler': 4.5.2 + '@smithy/types': 4.14.0 + '@smithy/util-base64': 4.3.2 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 - '@sindresorhus/merge-streams@4.0.0': {} + '@smithy/util-uri-escape@4.2.2': + dependencies: + tslib: 2.8.1 - '@sinonjs/commons@3.0.1': + '@smithy/util-utf8@2.3.0': dependencies: - type-detect: 4.0.8 + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 - '@sinonjs/fake-timers@13.0.5': + '@smithy/util-utf8@4.2.2': dependencies: - '@sinonjs/commons': 3.0.1 + '@smithy/util-buffer-from': 4.2.2 + tslib: 2.8.1 - '@slorber/react-helmet-async@1.3.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@smithy/util-waiter@4.2.15': dependencies: - '@babel/runtime': 7.28.4 - invariant: 2.2.4 - prop-types: 15.8.1 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - react-fast-compare: 3.2.2 - shallowequal: 1.1.0 + '@smithy/types': 4.14.0 + tslib: 2.8.1 - '@slorber/remark-comment@1.0.0': + '@smithy/uuid@1.1.2': dependencies: - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 + tslib: 2.8.1 '@standard-schema/spec@1.0.0': {} + '@standard-schema/utils@0.3.0': {} + '@stoplight/better-ajv-errors@1.0.3(ajv@8.17.1)': dependencies: ajv: 8.17.1 @@ -14616,7 +17243,7 @@ snapshots: '@stoplight/spectral-rulesets': 1.22.0(encoding@0.1.13) '@stoplight/spectral-runtime': 1.1.4(encoding@0.1.13) '@stoplight/types': 13.20.0 - '@types/node': 25.0.3 + '@types/node': 25.5.2 pony-cause: 1.1.1 rollup: 2.79.2 tslib: 2.8.1 @@ -14633,7 +17260,7 @@ snapshots: '@stoplight/spectral-runtime': 1.1.4(encoding@0.1.13) '@stoplight/types': 13.20.0 '@stoplight/yaml': 4.2.3 - '@types/node': 25.0.3 + '@types/node': 25.5.2 ajv: 8.17.1 ast-types: 0.14.2 astring: 1.9.0 @@ -14708,106 +17335,219 @@ snapshots: '@stoplight/yaml-ast-parser': 0.0.50 tslib: 2.8.1 - '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 - '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.28.5)': + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@svgr/babel-preset@8.1.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.29.0) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.29.0) + + '@svgr/core@8.1.0(typescript@6.0.2)': + dependencies: + '@babel/core': 7.29.0 + '@svgr/babel-preset': 8.1.0(@babel/core@7.29.0) + camelcase: 6.3.0 + cosmiconfig: 8.3.6(typescript@6.0.2) + snake-case: 3.0.4 + transitivePeerDependencies: + - supports-color + - typescript + + '@svgr/hast-util-to-babel-ast@8.0.0': + dependencies: + '@babel/types': 7.29.0 + entities: 4.5.0 + + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@6.0.2))': + dependencies: + '@babel/core': 7.29.0 + '@svgr/babel-preset': 8.1.0(@babel/core@7.29.0) + '@svgr/core': 8.1.0(typescript@6.0.2) + '@svgr/hast-util-to-babel-ast': 8.0.0 + svg-parser: 2.0.4 + transitivePeerDependencies: + - supports-color + + '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@6.0.2))(typescript@6.0.2)': + dependencies: + '@svgr/core': 8.1.0(typescript@6.0.2) + cosmiconfig: 8.3.6(typescript@6.0.2) + deepmerge: 4.3.1 + svgo: 3.3.2 + transitivePeerDependencies: + - typescript + + '@svgr/webpack@8.1.0(typescript@6.0.2)': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-constant-elements': 7.27.1(@babel/core@7.29.0) + '@babel/preset-env': 7.29.2(@babel/core@7.29.0) + '@babel/preset-react': 7.28.5(@babel/core@7.29.0) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) + '@svgr/core': 8.1.0(typescript@6.0.2) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@6.0.2)) + '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@6.0.2))(typescript@6.0.2) + transitivePeerDependencies: + - supports-color + - typescript + + '@swc/core-darwin-arm64@1.15.24': + optional: true + + '@swc/core-darwin-x64@1.15.24': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.15.24': + optional: true + + '@swc/core-linux-arm64-gnu@1.15.24': + optional: true + + '@swc/core-linux-arm64-musl@1.15.24': + optional: true + + '@swc/core-linux-ppc64-gnu@1.15.24': + optional: true + + '@swc/core-linux-s390x-gnu@1.15.24': + optional: true + + '@swc/core-linux-x64-gnu@1.15.24': + optional: true + + '@swc/core-linux-x64-musl@1.15.24': + optional: true + + '@swc/core-win32-arm64-msvc@1.15.24': + optional: true + + '@swc/core-win32-ia32-msvc@1.15.24': + optional: true + + '@swc/core-win32-x64-msvc@1.15.24': + optional: true + + '@swc/core@1.15.24': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.26 + optionalDependencies: + '@swc/core-darwin-arm64': 1.15.24 + '@swc/core-darwin-x64': 1.15.24 + '@swc/core-linux-arm-gnueabihf': 1.15.24 + '@swc/core-linux-arm64-gnu': 1.15.24 + '@swc/core-linux-arm64-musl': 1.15.24 + '@swc/core-linux-ppc64-gnu': 1.15.24 + '@swc/core-linux-s390x-gnu': 1.15.24 + '@swc/core-linux-x64-gnu': 1.15.24 + '@swc/core-linux-x64-musl': 1.15.24 + '@swc/core-win32-arm64-msvc': 1.15.24 + '@swc/core-win32-ia32-msvc': 1.15.24 + '@swc/core-win32-x64-msvc': 1.15.24 + + '@swc/counter@0.1.3': {} + + '@swc/html-darwin-arm64@1.15.24': + optional: true + + '@swc/html-darwin-x64@1.15.24': + optional: true + + '@swc/html-linux-arm-gnueabihf@1.15.24': + optional: true + + '@swc/html-linux-arm64-gnu@1.15.24': + optional: true - '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 + '@swc/html-linux-arm64-musl@1.15.24': + optional: true - '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 + '@swc/html-linux-ppc64-gnu@1.15.24': + optional: true - '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 + '@swc/html-linux-s390x-gnu@1.15.24': + optional: true - '@svgr/babel-preset@8.1.0(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.28.5) - '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.28.5) - '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.28.5) - '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.28.5) - '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.28.5) - '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.28.5) - '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.28.5) - '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.28.5) + '@swc/html-linux-x64-gnu@1.15.24': + optional: true - '@svgr/core@8.1.0(typescript@5.9.3)': - dependencies: - '@babel/core': 7.28.5 - '@svgr/babel-preset': 8.1.0(@babel/core@7.28.5) - camelcase: 6.3.0 - cosmiconfig: 8.3.6(typescript@5.9.3) - snake-case: 3.0.4 - transitivePeerDependencies: - - supports-color - - typescript + '@swc/html-linux-x64-musl@1.15.24': + optional: true - '@svgr/hast-util-to-babel-ast@8.0.0': - dependencies: - '@babel/types': 7.28.5 - entities: 4.5.0 + '@swc/html-win32-arm64-msvc@1.15.24': + optional: true - '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.9.3))': - dependencies: - '@babel/core': 7.28.5 - '@svgr/babel-preset': 8.1.0(@babel/core@7.28.5) - '@svgr/core': 8.1.0(typescript@5.9.3) - '@svgr/hast-util-to-babel-ast': 8.0.0 - svg-parser: 2.0.4 - transitivePeerDependencies: - - supports-color + '@swc/html-win32-ia32-msvc@1.15.24': + optional: true - '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.9.3))(typescript@5.9.3)': - dependencies: - '@svgr/core': 8.1.0(typescript@5.9.3) - cosmiconfig: 8.3.6(typescript@5.9.3) - deepmerge: 4.3.1 - svgo: 3.3.2 - transitivePeerDependencies: - - typescript + '@swc/html-win32-x64-msvc@1.15.24': + optional: true - '@svgr/webpack@8.1.0(typescript@5.9.3)': + '@swc/html@1.15.24': dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-transform-react-constant-elements': 7.27.1(@babel/core@7.28.5) - '@babel/preset-env': 7.28.5(@babel/core@7.28.5) - '@babel/preset-react': 7.28.5(@babel/core@7.28.5) - '@babel/preset-typescript': 7.28.5(@babel/core@7.28.5) - '@svgr/core': 8.1.0(typescript@5.9.3) - '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3)) - '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3))(typescript@5.9.3) - transitivePeerDependencies: - - supports-color - - typescript + '@swc/counter': 0.1.3 + optionalDependencies: + '@swc/html-darwin-arm64': 1.15.24 + '@swc/html-darwin-x64': 1.15.24 + '@swc/html-linux-arm-gnueabihf': 1.15.24 + '@swc/html-linux-arm64-gnu': 1.15.24 + '@swc/html-linux-arm64-musl': 1.15.24 + '@swc/html-linux-ppc64-gnu': 1.15.24 + '@swc/html-linux-s390x-gnu': 1.15.24 + '@swc/html-linux-x64-gnu': 1.15.24 + '@swc/html-linux-x64-musl': 1.15.24 + '@swc/html-win32-arm64-msvc': 1.15.24 + '@swc/html-win32-ia32-msvc': 1.15.24 + '@swc/html-win32-x64-msvc': 1.15.24 + + '@swc/types@0.1.26': + dependencies: + '@swc/counter': 0.1.3 '@szmarczak/http-timer@5.0.1': dependencies: defer-to-connect: 2.0.1 - '@testcontainers/postgresql@11.11.0': + '@testcontainers/postgresql@11.13.0': dependencies: - testcontainers: 11.11.0 + testcontainers: 11.13.0 transitivePeerDependencies: - bare-abort-controller - bare-buffer @@ -14834,12 +17574,12 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/react@16.3.1(@testing-library/dom@10.4.1)(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react@19.2.7)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@babel/runtime': 7.28.4 '@testing-library/dom': 10.4.1 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) optionalDependencies: '@types/react': 19.2.7 @@ -14851,29 +17591,37 @@ snapshots: '@trysound/sax@0.2.0': {} - '@tsconfig/node10@1.0.12': - optional: true + '@ts-morph/common@0.11.1': + dependencies: + fast-glob: 3.3.3 + minimatch: 3.1.5 + mkdirp: 1.0.4 + path-browserify: 1.0.1 - '@tsconfig/node12@1.0.11': - optional: true + '@tsconfig/node10@1.0.12': {} - '@tsconfig/node14@1.0.3': - optional: true + '@tsconfig/node12@1.0.11': {} - '@tsconfig/node16@1.0.4': - optional: true + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} - '@ttoss/config@1.35.12': + '@ttoss/cloudformation@0.12.10': dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.5) - '@babel/preset-env': 7.28.5(@babel/core@7.28.5) - '@babel/preset-react': 7.28.5(@babel/core@7.28.5) - '@babel/preset-typescript': 7.28.5(@babel/core@7.28.5) - '@commitlint/config-conventional': 19.8.1 - '@formatjs/ts-transformer': 3.14.2 - babel-plugin-formatjs: 10.5.41 - babel-plugin-transform-import-meta: 2.3.3(@babel/core@7.28.5) + '@ttoss/read-config-file': 2.2.8 + js-yaml: 4.1.1 + + '@ttoss/config@1.37.8': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0) + '@babel/preset-env': 7.29.2(@babel/core@7.29.0) + '@babel/preset-react': 7.28.5(@babel/core@7.29.0) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) + '@commitlint/config-conventional': 20.5.0 + '@formatjs/ts-transformer': 4.4.3 + babel-plugin-formatjs: 11.3.2 + babel-plugin-transform-import-meta: 2.3.3(@babel/core@7.29.0) deepmerge: 4.3.1 identity-obj-proxy: 3.0.0 prettier-package-json: 2.8.0 @@ -14881,32 +17629,32 @@ snapshots: - supports-color - ts-jest - '@ttoss/eslint-config@1.26.6(@testing-library/dom@10.4.1)(@types/eslint@9.6.1)(@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(jest@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)))(prettier@3.7.4)(turbo@2.6.3)(typescript@5.9.3)': + '@ttoss/eslint-config@1.26.14(@testing-library/dom@10.4.1)(@types/eslint@9.6.1)(@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2))(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2))(eslint@9.39.2(jiti@2.6.1))(jest@30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2)))(prettier@3.8.1)(turbo@2.9.4)(typescript@6.0.2)': dependencies: - '@eslint/compat': 2.0.0(eslint@9.39.2(jiti@2.6.1)) - '@eslint/eslintrc': 3.3.3 + '@eslint/compat': 2.0.4(eslint@9.39.2(jiti@2.6.1)) + '@eslint/eslintrc': 3.3.5 '@eslint/js': 9.39.2 - '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2) eslint: 9.39.2(jiti@2.6.1) eslint-config-prettier: 10.1.8(eslint@9.39.2(jiti@2.6.1)) - eslint-config-turbo: 2.6.3(eslint@9.39.2(jiti@2.6.1))(turbo@2.6.3) + eslint-config-turbo: 2.9.4(eslint@9.39.2(jiti@2.6.1))(turbo@2.9.4) eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-formatjs: 5.4.2(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-jest: 29.5.0(@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(jest@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)))(typescript@5.9.3) + eslint-plugin-formatjs: 6.4.4(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-jest: 29.15.1(@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2))(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2))(eslint@9.39.2(jiti@2.6.1))(jest@30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2)))(typescript@6.0.2) eslint-plugin-jest-dom: 5.5.0(@testing-library/dom@10.4.1)(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-prefer-arrow-functions: 3.9.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - eslint-plugin-prettier: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.7.4) + eslint-plugin-prefer-arrow-functions: 3.9.1(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2) + eslint-plugin-prettier: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.1) eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react-hooks: 7.0.1(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react-namespace-import: 1.0.5(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-react-refresh: 0.4.25(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-react-refresh: 0.5.2(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-relay: 2.0.0 eslint-plugin-simple-import-sort: 12.1.1(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-unicorn: 62.0.0(eslint@9.39.2(jiti@2.6.1)) - globals: 16.5.0 - typescript-eslint: 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + eslint-plugin-unicorn: 64.0.0(eslint@9.39.2(jiti@2.6.1)) + globals: 17.4.0 + typescript-eslint: 8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2) transitivePeerDependencies: - '@testing-library/dom' - '@types/eslint' @@ -14920,45 +17668,46 @@ snapshots: - turbo - typescript - '@ttoss/http-server-mcp@0.3.2': + '@ttoss/http-server-mcp@0.11.1': dependencies: - '@modelcontextprotocol/sdk': 1.24.3(zod@3.25.76) - '@ttoss/http-server': 0.3.2 + '@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76) + '@ttoss/http-server': 0.5.9 zod: 3.25.76 transitivePeerDependencies: - '@cfworker/json-schema' - supports-color - '@ttoss/http-server@0.3.2': + '@ttoss/http-server@0.5.9': dependencies: '@koa/bodyparser': 6.0.0(koa@3.1.1) '@koa/cors': 5.0.0 '@koa/multer': 4.0.0(koa@3.1.1)(multer@2.0.2) '@koa/router': 15.1.1(koa@3.1.1) koa: 3.1.1 + koa-static: 5.0.0 multer: 2.0.2 transitivePeerDependencies: - supports-color '@ttoss/logger@0.7.1': {} - '@ttoss/monorepo@1.28.0': + '@ttoss/monorepo@1.29.8': dependencies: commander: 14.0.2 - '@ttoss/postgresdb-cli@0.1.24': + '@ttoss/postgresdb-cli@0.2.8': dependencies: commander: 14.0.2 dotenv: 17.2.3 - esbuild: 0.27.1 + esbuild: 0.27.7 sequelize-erd: 1.3.1 - '@ttoss/postgresdb@0.3.0(@types/node@25.0.3)(@types/validator@13.15.10)(reflect-metadata@0.2.2)': + '@ttoss/postgresdb@0.8.0(@types/node@25.5.2)(@types/validator@13.15.10)(reflect-metadata@0.2.2)': dependencies: - pg: 8.16.3 + pg: 8.20.0 pgvector: 0.2.1 - sequelize: 6.37.7(pg@8.16.3) - sequelize-typescript: 2.1.6(@types/node@25.0.3)(@types/validator@13.15.10)(reflect-metadata@0.2.2)(sequelize@6.37.7(pg@8.16.3)) + sequelize: 6.37.7(pg@8.20.0) + sequelize-typescript: 2.1.6(@types/node@25.5.2)(@types/validator@13.15.10)(reflect-metadata@0.2.2)(sequelize@6.37.7(pg@8.20.0)) transitivePeerDependencies: - '@types/node' - '@types/validator' @@ -14974,19 +17723,25 @@ snapshots: - supports-color - tedious - '@ttoss/test-utils@4.0.2(@types/jest@30.0.0)(@types/react@19.2.7)(encoding@0.1.13)(jest@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@ttoss/read-config-file@2.2.8': + dependencies: + esbuild: 0.27.7 + import-sync: 2.2.3 + js-yaml: 4.1.1 + + '@ttoss/test-utils@4.2.8(@types/jest@30.0.0)(@types/react@19.2.7)(encoding@0.1.13)(jest@30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2)))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@emotion/jest': 11.14.2(@types/jest@30.0.0) - '@faker-js/faker': 10.2.0 + '@faker-js/faker': 10.4.0 '@testing-library/dom': 10.4.1 '@testing-library/jest-dom': 6.9.1 - '@testing-library/react': 16.3.1(@testing-library/dom@10.4.1)(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@testing-library/react': 16.3.2(@testing-library/dom@10.4.1)(@types/react@19.2.7)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) '@types/relay-test-utils': 19.0.0 - jest: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) + jest: 30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2)) jest-environment-jsdom: 30.2.0 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) relay-test-utils: 20.1.1(encoding@0.1.13) resize-observer-polyfill: 1.5.1 transitivePeerDependencies: @@ -15000,6 +17755,24 @@ snapshots: - supports-color - utf-8-validate + '@turbo/darwin-64@2.9.4': + optional: true + + '@turbo/darwin-arm64@2.9.4': + optional: true + + '@turbo/linux-64@2.9.4': + optional: true + + '@turbo/linux-arm64@2.9.4': + optional: true + + '@turbo/windows-64@2.9.4': + optional: true + + '@turbo/windows-arm64@2.9.4': + optional: true + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 @@ -15010,14 +17783,14 @@ snapshots: '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 '@types/babel__helper-plugin-utils@7.10.3': dependencies: @@ -15026,40 +17799,38 @@ snapshots: '@types/babel__template@7.4.4': dependencies: '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 + + '@types/bcryptjs@3.0.0': + dependencies: + bcryptjs: 3.0.3 '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/bonjour@3.5.13': dependencies: - '@types/node': 25.0.3 - - '@types/caseless@0.12.5': {} + '@types/node': 25.5.2 '@types/co-body@6.1.3': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/qs': 6.14.0 '@types/connect-history-api-fallback@1.5.4': dependencies: '@types/express-serve-static-core': 4.19.7 - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/connect@3.4.38': dependencies: - '@types/node': 25.0.3 - - '@types/conventional-commits-parser@5.0.2': - dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/cookiejar@2.1.5': {} @@ -15069,18 +17840,18 @@ snapshots: '@types/docker-modem@3.0.6': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/ssh2': 1.15.5 - '@types/dockerode@3.3.47': + '@types/dockerode@4.0.1': dependencies: '@types/docker-modem': 3.0.6 - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/ssh2': 1.15.5 '@types/es-aggregate-error@1.0.6': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/eslint-scope@3.7.7': dependencies: @@ -15102,7 +17873,7 @@ snapshots: '@types/express-serve-static-core@4.19.7': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 @@ -15114,11 +17885,7 @@ snapshots: '@types/qs': 6.14.0 '@types/serve-static': 1.15.10 - '@types/gtag.js@0.0.12': {} - - '@types/hast@2.3.10': - dependencies: - '@types/unist': 2.0.11 + '@types/gtag.js@0.0.20': {} '@types/hast@3.0.4': dependencies: @@ -15126,11 +17893,6 @@ snapshots: '@types/history@4.7.11': {} - '@types/hoist-non-react-statics@3.3.7(@types/react@19.2.7)': - dependencies: - '@types/react': 19.2.7 - hoist-non-react-statics: 3.3.2 - '@types/html-minifier-terser@6.1.0': {} '@types/http-cache-semantics@4.0.4': {} @@ -15139,7 +17901,7 @@ snapshots: '@types/http-proxy@1.17.17': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/istanbul-lib-coverage@2.0.6': {} @@ -15158,7 +17920,7 @@ snapshots: '@types/jsdom@21.1.7': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/tough-cookie': 4.0.5 parse5: 7.3.0 @@ -15166,11 +17928,12 @@ snapshots: '@types/json5@0.0.29': {} - '@types/markdown-escape@1.1.3': {} - - '@types/mdast@3.0.15': + '@types/jsonwebtoken@9.0.10': dependencies: - '@types/unist': 2.0.11 + '@types/ms': 2.1.0 + '@types/node': 25.5.2 + + '@types/markdown-escape@1.1.3': {} '@types/mdast@4.0.4': dependencies: @@ -15186,7 +17949,9 @@ snapshots: '@types/node-forge@1.3.14': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 + + '@types/node@16.18.11': {} '@types/node@17.0.45': {} @@ -15198,9 +17963,9 @@ snapshots: dependencies: undici-types: 6.21.0 - '@types/node@25.0.3': + '@types/node@25.5.2': dependencies: - undici-types: 7.16.0 + undici-types: 7.18.2 '@types/normalize-package-data@2.4.4': {} @@ -15212,31 +17977,20 @@ snapshots: dependencies: parse-path: 7.1.0 - '@types/parse5@6.0.3': {} - - '@types/pg@8.16.0': + '@types/pg@8.20.0': dependencies: - '@types/node': 25.0.3 - pg-protocol: 1.10.3 + '@types/node': 25.5.2 + pg-protocol: 1.13.0 pg-types: 2.2.0 - '@types/picomatch@3.0.2': {} + '@types/picomatch@4.0.3': {} '@types/prismjs@1.26.5': {} - '@types/prop-types@15.7.15': {} - '@types/qs@6.14.0': {} '@types/range-parser@1.2.7': {} - '@types/react-redux@7.1.34': - dependencies: - '@types/hoist-non-react-statics': 3.3.7(@types/react@19.2.7) - '@types/react': 19.2.7 - hoist-non-react-statics: 3.3.2 - redux: 4.2.1 - '@types/react-relay@18.2.1': dependencies: '@types/react': 19.2.7 @@ -15271,29 +18025,22 @@ snapshots: '@types/react-relay': 18.2.1 '@types/relay-runtime': 20.1.0 - '@types/request@2.48.13': - dependencies: - '@types/caseless': 0.12.5 - '@types/node': 25.0.3 - '@types/tough-cookie': 4.0.5 - form-data: 2.5.5 - '@types/retry@0.12.2': {} '@types/sarif@2.1.7': {} '@types/sax@1.2.7': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/send@0.17.6': dependencies: '@types/mime': 1.3.5 - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/send@1.2.1': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/serve-index@1.9.4': dependencies: @@ -15302,20 +18049,20 @@ snapshots: '@types/serve-static@1.15.10': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/send': 0.17.6 '@types/sockjs@0.3.36': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/ssh2-streams@0.1.13': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/ssh2@0.5.52': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/ssh2-streams': 0.1.13 '@types/ssh2@1.15.5': @@ -15328,10 +18075,10 @@ snapshots: dependencies: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 - '@types/node': 25.0.3 + '@types/node': 25.5.2 form-data: 4.0.5 - '@types/supertest@6.0.3': + '@types/supertest@7.2.0': dependencies: '@types/methods': 1.1.4 '@types/superagent': 8.1.9 @@ -15344,13 +18091,13 @@ snapshots: '@types/urijs@1.19.26': {} - '@types/uuid@10.0.0': {} + '@types/use-sync-external-store@0.0.6': {} '@types/validator@13.15.10': {} '@types/ws@8.18.1': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/yargs-parser@21.0.3': {} @@ -15358,40 +18105,49 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2))(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.49.0 + '@typescript-eslint/parser': 8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/scope-manager': 8.58.0 + '@typescript-eslint/type-utils': 8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/visitor-keys': 8.58.0 eslint: 9.39.2(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.9.3) - typescript: 5.9.3 + ts-api-utils: 2.5.0(typescript@6.0.2) + typescript: 6.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2)': dependencies: - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.49.0 + '@typescript-eslint/scope-manager': 8.58.0 + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/typescript-estree': 8.58.0(typescript@6.0.2) + '@typescript-eslint/visitor-keys': 8.58.0 debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) - typescript: 5.9.3 + typescript: 6.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.49.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.49.0(typescript@6.0.2)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@6.0.2) '@typescript-eslint/types': 8.49.0 debug: 4.4.3 - typescript: 5.9.3 + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.58.0(typescript@6.0.2)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.58.0(typescript@6.0.2) + '@typescript-eslint/types': 8.58.0 + debug: 4.4.3 + typescript: 6.0.2 transitivePeerDependencies: - supports-color @@ -15400,47 +18156,84 @@ snapshots: '@typescript-eslint/types': 8.49.0 '@typescript-eslint/visitor-keys': 8.49.0 - '@typescript-eslint/tsconfig-utils@8.49.0(typescript@5.9.3)': + '@typescript-eslint/scope-manager@8.58.0': dependencies: - typescript: 5.9.3 + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/visitor-keys': 8.58.0 - '@typescript-eslint/type-utils@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.49.0(typescript@6.0.2)': dependencies: - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + typescript: 6.0.2 + + '@typescript-eslint/tsconfig-utils@8.58.0(typescript@6.0.2)': + dependencies: + typescript: 6.0.2 + + '@typescript-eslint/type-utils@8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2)': + dependencies: + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/typescript-estree': 8.58.0(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2) debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) - ts-api-utils: 2.1.0(typescript@5.9.3) - typescript: 5.9.3 + ts-api-utils: 2.5.0(typescript@6.0.2) + typescript: 6.0.2 transitivePeerDependencies: - supports-color '@typescript-eslint/types@8.49.0': {} - '@typescript-eslint/typescript-estree@8.49.0(typescript@5.9.3)': + '@typescript-eslint/types@8.58.0': {} + + '@typescript-eslint/typescript-estree@8.49.0(typescript@6.0.2)': dependencies: - '@typescript-eslint/project-service': 8.49.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) + '@typescript-eslint/project-service': 8.49.0(typescript@6.0.2) + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@6.0.2) '@typescript-eslint/types': 8.49.0 '@typescript-eslint/visitor-keys': 8.49.0 debug: 4.4.3 minimatch: 9.0.5 - semver: 7.7.3 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.1.0(typescript@6.0.2) + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@8.58.0(typescript@6.0.2)': + dependencies: + '@typescript-eslint/project-service': 8.58.0(typescript@6.0.2) + '@typescript-eslint/tsconfig-utils': 8.58.0(typescript@6.0.2) + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/visitor-keys': 8.58.0 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.7.4 tinyglobby: 0.2.15 - ts-api-utils: 2.1.0(typescript@5.9.3) - typescript: 5.9.3 + ts-api-utils: 2.5.0(typescript@6.0.2) + typescript: 6.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1)) '@typescript-eslint/scope-manager': 8.49.0 '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.49.0(typescript@6.0.2) + eslint: 9.39.2(jiti@2.6.1) + typescript: 6.0.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.58.0 + '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/typescript-estree': 8.58.0(typescript@6.0.2) eslint: 9.39.2(jiti@2.6.1) - typescript: 5.9.3 + typescript: 6.0.2 transitivePeerDependencies: - supports-color @@ -15449,8 +18242,15 @@ snapshots: '@typescript-eslint/types': 8.49.0 eslint-visitor-keys: 4.2.1 + '@typescript-eslint/visitor-keys@8.58.0': + dependencies: + '@typescript-eslint/types': 8.58.0 + eslint-visitor-keys: 5.0.1 + '@ungap/structured-clone@1.3.0': {} + '@unicode/unicode-17.0.0@1.6.16': {} + '@unrs/resolver-binding-android-arm-eabi@1.11.1': optional: true @@ -15510,8 +18310,162 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true + '@vercel/build-utils@9.1.0': {} + + '@vercel/error-utils@2.0.3': {} + + '@vercel/fun@1.1.2(encoding@0.1.13)': + dependencies: + '@tootallnate/once': 2.0.0 + async-listen: 1.2.0 + debug: 4.3.4 + execa: 3.2.0 + fs-extra: 8.1.0 + generic-pool: 3.4.2 + micro: 9.3.5-canary.3 + ms: 2.1.1 + node-fetch: 2.6.7(encoding@0.1.13) + path-match: 1.2.4 + promisepipe: 3.0.0 + semver: 7.5.4 + stat-mode: 0.3.0 + stream-to-promise: 2.2.0 + tar: 4.4.18 + tree-kill: 1.2.2 + uid-promise: 1.0.0 + uuid: 3.3.2 + xdg-app-paths: 5.1.0 + yauzl-promise: 2.1.3 + transitivePeerDependencies: + - encoding + - supports-color + + '@vercel/gatsby-plugin-vercel-analytics@1.0.11': + dependencies: + web-vitals: 0.2.4 + + '@vercel/gatsby-plugin-vercel-builder@2.0.65': + dependencies: + '@sinclair/typebox': 0.25.24 + '@vercel/build-utils': 9.1.0 + '@vercel/routing-utils': 5.0.1 + esbuild: 0.14.47 + etag: 1.8.1 + fs-extra: 11.1.0 + + '@vercel/go@3.2.1': {} + + '@vercel/hydrogen@1.0.11': + dependencies: + '@vercel/static-config': 3.0.0 + ts-morph: 12.0.0 + + '@vercel/next@4.4.4(encoding@0.1.13)(rollup@4.53.3)': + dependencies: + '@vercel/nft': 0.27.10(encoding@0.1.13)(rollup@4.53.3) + transitivePeerDependencies: + - encoding + - rollup + - supports-color + + '@vercel/nft@0.27.10(encoding@0.1.13)(rollup@4.53.3)': + dependencies: + '@mapbox/node-pre-gyp': 2.0.3(encoding@0.1.13) + '@rollup/pluginutils': 5.3.0(rollup@4.53.3) + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) + async-sema: 3.1.1 + bindings: 1.5.0 + estree-walker: 2.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + node-gyp-build: 4.8.4 + picomatch: 4.0.4 + resolve-from: 5.0.0 + transitivePeerDependencies: + - encoding + - rollup + - supports-color + + '@vercel/node@5.0.4(@swc/core@1.15.24)(encoding@0.1.13)(rollup@4.53.3)': + dependencies: + '@edge-runtime/node-utils': 2.3.0 + '@edge-runtime/primitives': 4.1.0 + '@edge-runtime/vm': 3.2.0 + '@types/node': 16.18.11 + '@vercel/build-utils': 9.1.0 + '@vercel/error-utils': 2.0.3 + '@vercel/nft': 0.27.10(encoding@0.1.13)(rollup@4.53.3) + '@vercel/static-config': 3.0.0 + async-listen: 3.0.0 + cjs-module-lexer: 1.2.3 + edge-runtime: 2.5.9 + es-module-lexer: 1.4.1 + esbuild: 0.14.47 + etag: 1.8.1 + node-fetch: 2.6.9(encoding@0.1.13) + path-to-regexp: 6.2.1 + path-to-regexp-updated: path-to-regexp@6.3.0 + ts-morph: 12.0.0 + ts-node: 10.9.1(@swc/core@1.15.24)(@types/node@16.18.11)(typescript@4.9.5) + typescript: 4.9.5 + undici: 5.28.4 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - encoding + - rollup + - supports-color + '@vercel/oidc@3.0.5': {} + '@vercel/python@4.7.1': {} + + '@vercel/redwood@2.1.13(encoding@0.1.13)(rollup@4.53.3)': + dependencies: + '@vercel/nft': 0.27.10(encoding@0.1.13)(rollup@4.53.3) + '@vercel/routing-utils': 5.0.1 + '@vercel/static-config': 3.0.0 + semver: 6.3.1 + ts-morph: 12.0.0 + transitivePeerDependencies: + - encoding + - rollup + - supports-color + + '@vercel/remix-builder@5.1.1(encoding@0.1.13)(rollup@4.53.3)': + dependencies: + '@vercel/error-utils': 2.0.3 + '@vercel/nft': 0.27.10(encoding@0.1.13)(rollup@4.53.3) + '@vercel/static-config': 3.0.0 + ts-morph: 12.0.0 + transitivePeerDependencies: + - encoding + - rollup + - supports-color + + '@vercel/routing-utils@5.0.1': + dependencies: + path-to-regexp: 6.1.0 + path-to-regexp-updated: path-to-regexp@6.3.0 + optionalDependencies: + ajv: 6.14.0 + + '@vercel/ruby@2.2.0': {} + + '@vercel/static-build@2.5.43': + dependencies: + '@vercel/gatsby-plugin-vercel-analytics': 1.0.11 + '@vercel/gatsby-plugin-vercel-builder': 2.0.65 + '@vercel/static-config': 3.0.0 + ts-morph: 12.0.0 + + '@vercel/static-config@3.0.0': + dependencies: + ajv: 8.6.3 + json-schema-to-ts: 1.6.4 + ts-morph: 12.0.0 + '@webassemblyjs/ast@1.14.1': dependencies: '@webassemblyjs/helper-numbers': 1.13.2 @@ -15592,10 +18546,7 @@ snapshots: '@xtuc/long@4.2.2': {} - JSONStream@1.3.5: - dependencies: - jsonparse: 1.3.1 - through: 2.3.8 + abbrev@3.0.1: {} abbrev@4.0.0: {} @@ -15613,6 +18564,10 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 + acorn-import-attributes@1.9.5(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn-import-phases@1.0.4(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -15629,11 +18584,7 @@ snapshots: address@1.2.2: {} - agent-base@6.0.2: - dependencies: - debug: 4.4.3 - transitivePeerDependencies: - - supports-color + adm-zip@0.5.17: {} agent-base@7.1.4: {} @@ -15650,10 +18601,6 @@ snapshots: '@opentelemetry/api': 1.9.0 zod: 4.1.13 - ajv-draft-04@1.0.0(ajv@8.11.0): - optionalDependencies: - ajv: 8.11.0 - ajv-draft-04@1.0.0(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 @@ -15662,21 +18609,21 @@ snapshots: dependencies: ajv: 8.17.1 - ajv-formats@2.1.1(ajv@8.11.0): - optionalDependencies: - ajv: 8.11.0 - ajv-formats@2.1.1(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 + ajv-formats@3.0.1(@redocly/ajv@8.18.0): + optionalDependencies: + ajv: '@redocly/ajv@8.18.0' + ajv-formats@3.0.1(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 - ajv-keywords@3.5.2(ajv@6.12.6): + ajv-keywords@3.5.2(ajv@6.14.0): dependencies: - ajv: 6.12.6 + ajv: 6.14.0 ajv-keywords@5.1.0(ajv@8.17.1): dependencies: @@ -15690,11 +18637,11 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.11.0: + ajv@6.14.0: dependencies: fast-deep-equal: 3.1.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 uri-js: 4.4.1 ajv@8.17.1: @@ -15704,6 +18651,13 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + ajv@8.6.3: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + algoliasearch-helper@3.27.0(algoliasearch@5.46.2): dependencies: '@algolia/events': 4.0.1 @@ -15734,8 +18688,6 @@ snapshots: dependencies: string-width: 4.2.3 - ansi-colors@4.1.3: {} - ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 @@ -15792,8 +18744,11 @@ snapshots: - bare-abort-controller - react-native-b4a - arg@4.1.3: - optional: true + are-we-there-yet@4.0.2: {} + + arg@4.1.0: {} + + arg@4.1.3: {} arg@5.0.2: {} @@ -15882,8 +18837,6 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 - arrify@2.0.1: {} - as-table@1.0.55: dependencies: printable-characters: 1.0.42 @@ -15904,22 +18857,20 @@ snapshots: async-function@1.0.0: {} - async-lock@1.4.1: {} + async-listen@1.2.0: {} - async-retry@1.3.3: - dependencies: - retry: 0.13.1 + async-listen@3.0.0: {} + + async-listen@3.0.1: {} - async@3.2.2: {} + async-lock@1.4.1: {} - async@3.2.4: {} + async-sema@3.1.1: {} async@3.2.6: {} asynckit@0.4.0: {} - at-least-node@1.0.0: {} - author-regex@1.0.0: {} autoprefixer@10.4.23(postcss@8.5.6): @@ -15950,47 +18901,54 @@ snapshots: axe-core@4.11.0: {} + axios@1.15.0: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + axobject-query@4.1.0: {} b4a@1.7.3: {} - babel-jest@30.2.0(@babel/core@7.28.5): + babel-jest@30.3.0(@babel/core@7.28.5): dependencies: '@babel/core': 7.28.5 - '@jest/transform': 30.2.0 + '@jest/transform': 30.3.0 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 7.0.1 - babel-preset-jest: 30.2.0(@babel/core@7.28.5) + babel-preset-jest: 30.3.0(@babel/core@7.28.5) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 transitivePeerDependencies: - supports-color - babel-loader@9.2.1(@babel/core@7.28.5)(webpack@5.104.1): + babel-loader@9.2.1(@babel/core@7.29.0)(webpack@5.104.1(@swc/core@1.15.24)): dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 find-cache-dir: 4.0.0 schema-utils: 4.3.3 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) babel-plugin-dynamic-import-node@2.3.3: dependencies: object.assign: 4.1.7 - babel-plugin-formatjs@10.5.41: + babel-plugin-formatjs@11.3.2: dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.0) '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 - '@formatjs/icu-messageformat-parser': 2.11.4 - '@formatjs/ts-transformer': 3.14.2 + '@babel/types': 7.29.0 + '@formatjs/icu-messageformat-parser': 3.5.3 + '@formatjs/ts-transformer': 4.4.3 '@types/babel__core': 7.20.5 '@types/babel__helper-plugin-utils': 7.10.3 '@types/babel__traverse': 7.28.0 - tslib: 2.8.1 transitivePeerDependencies: - supports-color - ts-jest @@ -16005,37 +18963,45 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-jest-hoist@30.2.0: + babel-plugin-jest-hoist@30.3.0: dependencies: '@types/babel__core': 7.20.5 - babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.5): + babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.0): dependencies: - '@babel/compat-data': 7.28.5 - '@babel/core': 7.28.5 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.5) + '@babel/compat-data': 7.29.0 + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.28.5): + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.0): dependencies: - '@babel/core': 7.28.5 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.5) - core-js-compat: 3.47.0 + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + core-js-compat: 3.49.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.28.5): + babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.0): dependencies: - '@babel/core': 7.28.5 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + core-js-compat: 3.49.0 transitivePeerDependencies: - supports-color - babel-plugin-transform-import-meta@2.3.3(@babel/core@7.28.5): + babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.0): dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + babel-plugin-transform-import-meta@2.3.3(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 '@babel/template': 7.27.2 tslib: 2.8.1 @@ -16058,16 +19024,18 @@ snapshots: '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.5) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.5) - babel-preset-jest@30.2.0(@babel/core@7.28.5): + babel-preset-jest@30.3.0(@babel/core@7.28.5): dependencies: '@babel/core': 7.28.5 - babel-plugin-jest-hoist: 30.2.0 + babel-plugin-jest-hoist: 30.3.0 babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.5) bail@2.0.2: {} balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + bare-events@2.8.2: {} bare-fs@4.5.2: @@ -16115,14 +19083,18 @@ snapshots: dependencies: tweetnacl: 0.14.5 + bcryptjs@3.0.3: {} + before-after-hook@4.0.0: {} big.js@5.2.2: {} - bignumber.js@9.3.1: {} - binary-extensions@2.3.0: {} + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -16139,7 +19111,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.14.0 + qs: 6.14.1 raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 @@ -16167,6 +19139,8 @@ snapshots: boolbase@1.0.0: {} + bowser@2.14.1: {} + boxen@6.2.1: dependencies: ansi-align: 3.0.1 @@ -16198,6 +19172,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -16214,6 +19192,8 @@ snapshots: dependencies: node-int64: 0.4.0 + buffer-crc32@0.2.13: {} + buffer-crc32@1.0.0: {} buffer-equal-constant-time@1.0.1: {} @@ -16223,9 +19203,14 @@ snapshots: buffer@4.9.2: dependencies: base64-js: 1.5.1 - ieee754: 1.1.13 + ieee754: 1.2.1 isarray: 1.0.0 + buffer@5.6.0: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -16260,6 +19245,8 @@ snapshots: bytes@3.0.0: {} + bytes@3.1.0: {} + bytes@3.1.2: {} cac@6.7.14: {} @@ -16331,11 +19318,49 @@ snapshots: caniuse-lite@1.0.30001760: {} - ccount@2.0.1: {} + carlin@1.48.3(@swc/core@1.15.24)(@types/node@25.5.2)(encoding@0.1.13)(rollup@4.53.3)(typescript@6.0.2): + dependencies: + '@aws-sdk/client-cloudformation': 3.1029.0 + '@aws-sdk/client-s3': 3.1029.0 + '@aws-sdk/lib-storage': 3.1029.0(@aws-sdk/client-s3@3.1029.0) + '@octokit/webhooks': 12.3.2 + '@slack/webhook': 7.0.8 + '@ttoss/cloudformation': 0.12.10 + '@ttoss/config': 1.37.8 + '@ttoss/read-config-file': 2.2.8 + adm-zip: 0.5.17 + aws-sdk: 2.1693.0 + change-case: 5.4.4 + deep-equal: 2.2.3 + deepmerge: 4.3.1 + dotenv: 17.4.1 + esbuild: 0.27.7 + findup-sync: 5.0.0 + glob: 11.1.0 + import-sync: 2.2.3 + js-yaml: 4.1.1 + mime-types: 2.1.35 + npmlog: 7.0.1 + prettier: 3.8.1 + semver: 7.7.4 + simple-git: 3.36.0 + ts-node: 10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2) + uglify-js: 3.19.3 + vercel: 39.4.2(@swc/core@1.15.24)(encoding@0.1.13)(rollup@4.53.3) + yargs: 17.7.2 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - aws-crt + - debug + - encoding + - rollup + - supports-color + - ts-jest + - typescript - chalk-template@1.1.2: - dependencies: - chalk: 5.6.2 + ccount@2.0.1: {} chalk@4.1.2: dependencies: @@ -16389,6 +19414,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.0: + dependencies: + readdirp: 4.1.2 + chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -16403,6 +19432,10 @@ snapshots: ci-info@4.3.1: {} + ci-info@4.4.0: {} + + cjs-module-lexer@1.2.3: {} + cjs-module-lexer@2.1.1: {} clean-css@5.3.3: @@ -16421,8 +19454,6 @@ snapshots: dependencies: restore-cursor: 5.1.0 - cli-spinners@2.9.2: {} - cli-table3@0.6.5: dependencies: string-width: 4.2.3 @@ -16462,8 +19493,6 @@ snapshots: clone@1.0.4: {} - clsx@1.2.1: {} - clsx@2.1.1: {} co-body@6.2.0: @@ -16476,6 +19505,8 @@ snapshots: co@4.6.0: {} + code-block-writer@10.1.1: {} + collapse-white-space@2.1.0: {} collect-v8-coverage@1.0.3: {} @@ -16486,6 +19517,8 @@ snapshots: color-name@1.1.4: {} + color-support@1.1.3: {} + colord@2.9.3: {} colorette@1.4.0: {} @@ -16507,10 +19540,10 @@ snapshots: commander@10.0.1: {} - commander@13.1.0: {} - commander@14.0.2: {} + commander@14.0.3: {} + commander@2.20.3: {} commander@4.1.1: {} @@ -16597,6 +19630,8 @@ snapshots: consola@3.4.2: {} + console-control-strings@1.1.0: {} + content-disposition@0.5.2: {} content-disposition@0.5.4: @@ -16605,36 +19640,36 @@ snapshots: content-disposition@1.0.1: {} - content-type@1.0.5: {} + content-type@1.0.4: {} - conventional-changelog-angular@7.0.0: - dependencies: - compare-func: 2.0.0 + content-type@1.0.5: {} - conventional-changelog-angular@8.1.0: + conventional-changelog-angular@8.3.1: dependencies: compare-func: 2.0.0 - conventional-changelog-conventionalcommits@7.0.2: + conventional-changelog-conventionalcommits@9.3.1: dependencies: compare-func: 2.0.0 conventional-changelog-preset-loader@5.0.0: {} - conventional-changelog-writer@8.2.0: + conventional-changelog-writer@8.4.0: dependencies: + '@simple-libs/stream-utils': 1.2.0 conventional-commits-filter: 5.0.0 handlebars: 4.7.8 meow: 13.2.0 - semver: 7.7.3 + semver: 7.7.4 - conventional-changelog@7.1.1(conventional-commits-filter@5.0.0): + conventional-changelog@7.2.0(conventional-commits-filter@5.0.0): dependencies: - '@conventional-changelog/git-client': 2.5.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.2.1) + '@conventional-changelog/git-client': 2.6.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0) + '@simple-libs/hosted-git-info': 1.0.2 '@types/normalize-package-data': 2.4.4 conventional-changelog-preset-loader: 5.0.0 - conventional-changelog-writer: 8.2.0 - conventional-commits-parser: 6.2.1 + conventional-changelog-writer: 8.4.0 + conventional-commits-parser: 6.4.0 fd-package-json: 2.0.0 meow: 13.2.0 normalize-package-data: 7.0.1 @@ -16643,25 +19678,21 @@ snapshots: conventional-commits-filter@5.0.0: {} - conventional-commits-parser@5.0.0: - dependencies: - JSONStream: 1.3.5 - is-text-path: 2.0.0 - meow: 12.1.1 - split2: 4.2.0 - - conventional-commits-parser@6.2.1: + conventional-commits-parser@6.4.0: dependencies: + '@simple-libs/stream-utils': 1.2.0 meow: 13.2.0 conventional-recommended-bump@11.2.0: dependencies: - '@conventional-changelog/git-client': 2.5.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.2.1) + '@conventional-changelog/git-client': 2.6.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0) conventional-changelog-preset-loader: 5.0.0 conventional-commits-filter: 5.0.0 - conventional-commits-parser: 6.2.1 + conventional-commits-parser: 6.4.0 meow: 13.2.0 + convert-hrtime@3.0.0: {} + convert-source-map@2.0.0: {} cookie-signature@1.0.7: {} @@ -16679,7 +19710,7 @@ snapshots: copy-text-to-clipboard@3.2.2: {} - copy-webpack-plugin@11.0.0(webpack@5.104.1): + copy-webpack-plugin@11.0.0(webpack@5.104.1(@swc/core@1.15.24)): dependencies: fast-glob: 3.3.3 glob-parent: 6.0.2 @@ -16687,14 +19718,12 @@ snapshots: normalize-path: 3.0.0 schema-utils: 4.3.3 serialize-javascript: 6.0.2 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) - core-js-compat@3.47.0: + core-js-compat@3.49.0: dependencies: browserslist: 4.28.1 - core-js-pure@3.47.0: {} - core-js@3.47.0: {} core-util-is@1.0.3: {} @@ -16704,12 +19733,12 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 - cosmiconfig-typescript-loader@6.2.0(@types/node@25.0.3)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3): + cosmiconfig-typescript-loader@6.2.0(@types/node@25.5.2)(cosmiconfig@9.0.1(typescript@6.0.2))(typescript@6.0.2): dependencies: - '@types/node': 25.0.3 - cosmiconfig: 9.0.0(typescript@5.9.3) + '@types/node': 25.5.2 + cosmiconfig: 9.0.1(typescript@6.0.2) jiti: 2.6.1 - typescript: 5.9.3 + typescript: 6.0.2 cosmiconfig@7.1.0: dependencies: @@ -16719,23 +19748,23 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 - cosmiconfig@8.3.6(typescript@5.9.3): + cosmiconfig@8.3.6(typescript@6.0.2): dependencies: import-fresh: 3.3.1 js-yaml: 4.1.1 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: - typescript: 5.9.3 + typescript: 6.0.2 - cosmiconfig@9.0.0(typescript@5.9.3): + cosmiconfig@9.0.1(typescript@6.0.2): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 js-yaml: 4.1.1 parse-json: 5.2.0 optionalDependencies: - typescript: 5.9.3 + typescript: 6.0.2 cpu-features@0.0.10: dependencies: @@ -16750,8 +19779,7 @@ snapshots: crc-32: 1.2.2 readable-stream: 4.7.0 - create-require@1.1.1: - optional: true + create-require@1.1.1: {} cross-fetch@3.2.0(encoding@0.1.13): dependencies: @@ -16787,7 +19815,7 @@ snapshots: postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - css-loader@6.11.0(webpack@5.104.1): + css-loader@6.11.0(@rspack/core@1.7.11)(webpack@5.104.1(@swc/core@1.15.24)): dependencies: icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 @@ -16796,11 +19824,12 @@ snapshots: postcss-modules-scope: 3.2.1(postcss@8.5.6) postcss-modules-values: 4.0.0(postcss@8.5.6) postcss-value-parser: 4.2.0 - semver: 7.7.3 + semver: 7.7.4 optionalDependencies: - webpack: 5.104.1 + '@rspack/core': 1.7.11 + webpack: 5.104.1(@swc/core@1.15.24) - css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(webpack@5.104.1): + css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(webpack@5.104.1(@swc/core@1.15.24)): dependencies: '@jridgewell/trace-mapping': 0.3.31 cssnano: 6.1.2(postcss@8.5.6) @@ -16808,7 +19837,7 @@ snapshots: postcss: 8.5.6 schema-utils: 4.3.3 serialize-javascript: 6.0.2 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) optionalDependencies: clean-css: 5.3.3 @@ -16918,8 +19947,6 @@ snapshots: damerau-levenshtein@1.0.8: {} - dargs@8.1.0: {} - data-uri-to-buffer@2.0.2: {} data-urls@5.0.0: @@ -16955,6 +19982,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.3.4: + dependencies: + ms: 2.1.2 + debug@4.4.3: dependencies: ms: 2.1.3 @@ -16971,14 +20002,35 @@ snapshots: dedent@1.7.0: {} + dedent@1.7.2: {} + deep-equal@1.0.1: {} + deep-equal@2.2.3: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + es-get-iterator: 1.1.3 + get-intrinsic: 1.3.0 + is-arguments: 1.2.0 + is-array-buffer: 3.0.5 + is-date-object: 1.1.0 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.4 + isarray: 2.0.5 + object-is: 1.1.6 + object-keys: 1.1.1 + object.assign: 4.1.7 + regexp.prototype.flags: 1.5.4 + side-channel: 1.1.0 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + deep-extend@0.6.0: {} deep-is@0.1.4: {} - deepmerge-ts@7.1.5: {} - deepmerge@4.3.1: {} default-browser-id@5.0.1: {} @@ -17020,15 +20072,21 @@ snapshots: dependency-graph@0.11.0: {} + deprecation@2.3.1: {} + dequal@2.0.3: {} destroy@1.2.0: {} + detect-file@1.0.0: {} + detect-indent@7.0.2: {} detect-libc@1.0.3: optional: true + detect-libc@2.1.2: {} + detect-newline@3.1.0: {} detect-node@2.1.0: {} @@ -17053,10 +20111,7 @@ snapshots: asap: 2.0.6 wrappy: 1.0.2 - diff@4.0.2: - optional: true - - diff@5.2.0: {} + diff@4.0.2: {} dir-glob@3.0.1: dependencies: @@ -17066,7 +20121,7 @@ snapshots: dependencies: '@leichtgewicht/ip-codec': 2.0.5 - docker-compose@1.3.0: + docker-compose@1.4.2: dependencies: yaml: 2.8.2 @@ -17095,81 +20150,82 @@ snapshots: dependencies: esutils: 2.0.3 - docusaurus-plugin-openapi-docs@4.5.1(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(@docusaurus/utils-validation@3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@docusaurus/utils@3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(encoding@0.1.13)(react@19.2.3): + docusaurus-plugin-openapi-docs@5.0.0(@docusaurus/plugin-content-docs@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2))(@docusaurus/utils-validation@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@docusaurus/utils@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/json-schema@7.0.15)(encoding@0.1.13)(react@19.2.5): dependencies: - '@apidevtools/json-schema-ref-parser': 11.9.3 - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) - '@docusaurus/utils': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@docusaurus/utils-validation': 3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@redocly/openapi-core': 1.34.6 + '@apidevtools/json-schema-ref-parser': 15.3.5(@types/json-schema@7.0.15) + '@docusaurus/plugin-content-docs': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) + '@docusaurus/utils': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@docusaurus/utils-validation': 3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@redocly/openapi-core': 2.26.0 allof-merge: 0.6.7 - chalk: 4.1.2 - clsx: 1.2.1 - fs-extra: 9.1.0 + chalk: 5.6.2 + clsx: 2.1.1 + fs-extra: 11.3.4 json-pointer: 0.6.2 json5: 2.2.3 lodash: 4.17.21 mustache: 4.2.0 - openapi-to-postmanv2: 4.25.0(encoding@0.1.13) - postman-collection: 4.5.0 - react: 19.2.3 + openapi-to-postmanv2: 6.0.0(encoding@0.1.13) + postman-collection: 5.3.0 + react: 19.2.5 slugify: 1.6.6 swagger2openapi: 7.0.8(encoding@0.1.13) - xml-formatter: 2.6.1 + xml-formatter: 3.7.0 transitivePeerDependencies: + - '@types/json-schema' - encoding - - supports-color - docusaurus-plugin-sass@0.2.6(@docusaurus/core@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(sass@1.97.2)(webpack@5.104.1): + docusaurus-plugin-sass@0.2.6(@docusaurus/core@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2))(@rspack/core@1.7.11)(sass@1.97.2)(webpack@5.104.1(@swc/core@1.15.24)): dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + '@docusaurus/core': 3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2) sass: 1.97.2 - sass-loader: 16.0.6(sass@1.97.2)(webpack@5.104.1) + sass-loader: 16.0.6(@rspack/core@1.7.11)(sass@1.97.2)(webpack@5.104.1(@swc/core@1.15.24)) transitivePeerDependencies: - '@rspack/core' - node-sass - sass-embedded - webpack - docusaurus-theme-openapi-docs@4.5.1(@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(docusaurus-plugin-openapi-docs@4.5.1(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(@docusaurus/utils-validation@3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@docusaurus/utils@3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(encoding@0.1.13)(react@19.2.3))(docusaurus-plugin-sass@0.2.6(@docusaurus/core@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(sass@1.97.2)(webpack@5.104.1))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.104.1): + docusaurus-theme-openapi-docs@5.0.0(255c652d32e779b619b35f7801205cba): dependencies: - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@hookform/error-message': 2.0.1(react-dom@19.2.3(react@19.2.3))(react-hook-form@7.70.0(react@19.2.3))(react@19.2.3) - '@reduxjs/toolkit': 1.9.7(react-redux@7.2.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) + '@docusaurus/theme-common': 3.10.0(@docusaurus/plugin-content-docs@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2))(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@hookform/error-message': 2.0.1(react-dom@19.2.5(react@19.2.5))(react-hook-form@7.70.0(react@19.2.5))(react@19.2.5) + '@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@19.2.7)(react@19.2.5)(redux@5.0.1))(react@19.2.5) allof-merge: 0.6.7 buffer: 6.0.3 - clsx: 1.2.1 + clsx: 2.1.1 copy-text-to-clipboard: 3.2.2 crypto-js: 4.2.0 - docusaurus-plugin-openapi-docs: 4.5.1(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(@docusaurus/utils-validation@3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@docusaurus/utils@3.9.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(encoding@0.1.13)(react@19.2.3) - docusaurus-plugin-sass: 0.2.6(@docusaurus/core@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(sass@1.97.2)(webpack@5.104.1) + docusaurus-plugin-openapi-docs: 5.0.0(@docusaurus/plugin-content-docs@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2))(@docusaurus/utils-validation@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@docusaurus/utils@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/json-schema@7.0.15)(encoding@0.1.13)(react@19.2.5) + docusaurus-plugin-sass: 0.2.6(@docusaurus/core@3.10.0(@docusaurus/faster@3.10.0(@docusaurus/types@3.10.0(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)))(@mdx-js/react@3.1.1(@types/react@19.2.7)(react@19.2.5))(@rspack/core@1.7.11)(@swc/core@1.15.24)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(typescript@6.0.2))(@rspack/core@1.7.11)(sass@1.97.2)(webpack@5.104.1(@swc/core@1.15.24)) file-saver: 2.0.5 lodash: 4.17.21 pako: 2.1.0 - postman-code-generators: 1.14.2 - postman-collection: 4.5.0 - prism-react-renderer: 2.4.1(react@19.2.3) + path-browserify: 1.0.1 + postman-code-generators: 2.1.1 + postman-collection: 5.3.0 + prism-react-renderer: 2.4.1(react@19.2.5) process: 0.11.10 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - react-hook-form: 7.70.0(react@19.2.3) - react-live: 4.1.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + react-hook-form: 7.70.0(react@19.2.5) + react-live: 4.1.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5) react-magic-dropzone: 1.0.1 - react-markdown: 8.0.7(@types/react@19.2.7)(react@19.2.3) - react-modal: 3.16.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - react-redux: 7.2.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - rehype-raw: 6.1.1 - remark-gfm: 3.0.1 + react-markdown: 10.1.0(@types/react@19.2.7)(react@19.2.5) + react-modal: 3.16.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react-redux: 9.2.0(@types/react@19.2.7)(react@19.2.5)(redux@5.0.1) + rehype-raw: 7.0.0 + remark-gfm: 4.0.1 sass: 1.97.2 - sass-loader: 16.0.6(sass@1.97.2)(webpack@5.104.1) + sass-loader: 16.0.6(@rspack/core@1.7.11)(sass@1.97.2)(webpack@5.104.1(@swc/core@1.15.24)) unist-util-visit: 5.0.0 url: 0.11.4 - xml-formatter: 2.6.1 + xml-formatter: 3.7.0 transitivePeerDependencies: - '@rspack/core' - '@types/react' - node-sass - - react-native + - redux - sass-embedded - supports-color - webpack @@ -17233,6 +20289,8 @@ snapshots: dotenv@17.2.3: {} + dotenv@17.4.1: {} + dottie@2.0.6: {} dunder-proto@1.0.1: @@ -17243,32 +20301,30 @@ snapshots: duplexer@0.1.2: {} - duplexify@4.1.3: - dependencies: - end-of-stream: 1.4.5 - inherits: 2.0.4 - readable-stream: 3.6.2 - stream-shift: 1.0.3 - eastasianwidth@0.2.0: {} ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer: 5.2.1 - ee-first@1.1.1: {} - - effect@3.19.12: + edge-runtime@2.5.9: dependencies: - '@standard-schema/spec': 1.0.0 - fast-check: 3.23.2 + '@edge-runtime/format': 2.2.1 + '@edge-runtime/ponyfill': 2.4.2 + '@edge-runtime/vm': 3.2.0 + async-listen: 3.0.1 + mri: 1.2.0 + picocolors: 1.0.0 + pretty-ms: 7.0.1 + signal-exit: 4.0.2 + time-span: 4.0.0 + + ee-first@1.1.1: {} electron-to-chromium@1.5.267: {} emittery@0.13.1: {} - emoji-regex-xs@2.0.1: {} - emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} @@ -17288,6 +20344,10 @@ snapshots: iconv-lite: 0.6.3 optional: true + end-of-stream@1.1.0: + dependencies: + once: 1.3.3 + end-of-stream@1.4.5: dependencies: once: 1.4.0 @@ -17297,11 +20357,6 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 - enquirer@2.4.1: - dependencies: - ansi-colors: 4.1.3 - strip-ansi: 6.0.1 - entities@2.2.0: {} entities@4.5.0: {} @@ -17390,6 +20445,18 @@ snapshots: es-errors@1.3.0: {} + es-get-iterator@1.1.3: + dependencies: + call-bind: 1.0.8 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + is-arguments: 1.2.0 + is-map: 2.0.3 + is-set: 2.0.3 + is-string: 1.1.1 + isarray: 2.0.5 + stop-iteration-iterator: 1.1.0 + es-iterator-helpers@1.2.2: dependencies: call-bind: 1.0.8 @@ -17409,6 +20476,8 @@ snapshots: iterator.prototype: 1.1.5 safe-array-concat: 1.1.3 + es-module-lexer@1.4.1: {} + es-module-lexer@2.0.0: {} es-object-atoms@1.1.1: @@ -17448,6 +20517,89 @@ snapshots: esast-util-from-estree: 2.0.0 vfile-message: 4.0.3 + esbuild-android-64@0.14.47: + optional: true + + esbuild-android-arm64@0.14.47: + optional: true + + esbuild-darwin-64@0.14.47: + optional: true + + esbuild-darwin-arm64@0.14.47: + optional: true + + esbuild-freebsd-64@0.14.47: + optional: true + + esbuild-freebsd-arm64@0.14.47: + optional: true + + esbuild-linux-32@0.14.47: + optional: true + + esbuild-linux-64@0.14.47: + optional: true + + esbuild-linux-arm64@0.14.47: + optional: true + + esbuild-linux-arm@0.14.47: + optional: true + + esbuild-linux-mips64le@0.14.47: + optional: true + + esbuild-linux-ppc64le@0.14.47: + optional: true + + esbuild-linux-riscv64@0.14.47: + optional: true + + esbuild-linux-s390x@0.14.47: + optional: true + + esbuild-netbsd-64@0.14.47: + optional: true + + esbuild-openbsd-64@0.14.47: + optional: true + + esbuild-sunos-64@0.14.47: + optional: true + + esbuild-windows-32@0.14.47: + optional: true + + esbuild-windows-64@0.14.47: + optional: true + + esbuild-windows-arm64@0.14.47: + optional: true + + esbuild@0.14.47: + optionalDependencies: + esbuild-android-64: 0.14.47 + esbuild-android-arm64: 0.14.47 + esbuild-darwin-64: 0.14.47 + esbuild-darwin-arm64: 0.14.47 + esbuild-freebsd-64: 0.14.47 + esbuild-freebsd-arm64: 0.14.47 + esbuild-linux-32: 0.14.47 + esbuild-linux-64: 0.14.47 + esbuild-linux-arm: 0.14.47 + esbuild-linux-arm64: 0.14.47 + esbuild-linux-mips64le: 0.14.47 + esbuild-linux-ppc64le: 0.14.47 + esbuild-linux-riscv64: 0.14.47 + esbuild-linux-s390x: 0.14.47 + esbuild-netbsd-64: 0.14.47 + esbuild-openbsd-64: 0.14.47 + esbuild-sunos-64: 0.14.47 + esbuild-windows-32: 0.14.47 + esbuild-windows-64: 0.14.47 + esbuild-windows-arm64: 0.14.47 + esbuild@0.27.1: optionalDependencies: '@esbuild/aix-ppc64': 0.27.1 @@ -17477,6 +20629,35 @@ snapshots: '@esbuild/win32-ia32': 0.27.1 '@esbuild/win32-x64': 0.27.1 + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + escalade@3.2.0: {} escape-goat@4.0.0: {} @@ -17495,11 +20676,11 @@ snapshots: dependencies: eslint: 9.39.2(jiti@2.6.1) - eslint-config-turbo@2.6.3(eslint@9.39.2(jiti@2.6.1))(turbo@2.6.3): + eslint-config-turbo@2.9.4(eslint@9.39.2(jiti@2.6.1))(turbo@2.9.4): dependencies: eslint: 9.39.2(jiti@2.6.1) - eslint-plugin-turbo: 2.6.3(eslint@9.39.2(jiti@2.6.1))(turbo@2.6.3) - turbo: 2.6.3 + eslint-plugin-turbo: 2.9.4(eslint@9.39.2(jiti@2.6.1))(turbo@2.9.4) + turbo: 2.9.4 eslint-import-context@0.1.9(unrs-resolver@1.11.1): dependencies: @@ -17527,39 +20708,34 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2) eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-plugin-formatjs@5.4.2(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + eslint-plugin-formatjs@6.4.4(eslint@9.39.2(jiti@2.6.1)): dependencies: - '@formatjs/icu-messageformat-parser': 2.11.4 - '@formatjs/ts-transformer': 3.14.2 - '@types/eslint': 9.6.1 - '@types/picomatch': 3.0.2 - '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@formatjs/icu-messageformat-parser': 3.5.3 + '@formatjs/ts-transformer': 4.4.3 + '@types/picomatch': 4.0.3 + '@unicode/unicode-17.0.0': 1.6.16 eslint: 9.39.2(jiti@2.6.1) magic-string: 0.30.21 picomatch: 4.0.3 - tslib: 2.8.1 - unicode-emoji-utils: 1.3.1 transitivePeerDependencies: - - supports-color - ts-jest - - typescript - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -17570,7 +20746,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -17582,7 +20758,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -17596,16 +20772,16 @@ snapshots: optionalDependencies: '@testing-library/dom': 10.4.1 - eslint-plugin-jest@29.5.0(@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(jest@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)))(typescript@5.9.3): + eslint-plugin-jest@29.15.1(@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2))(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2))(eslint@9.39.2(jiti@2.6.1))(jest@30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2)))(typescript@6.0.2): dependencies: - '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2) eslint: 9.39.2(jiti@2.6.1) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - jest: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) + '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2))(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2) + jest: 30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2)) + typescript: 6.0.2 transitivePeerDependencies: - supports-color - - typescript eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.2(jiti@2.6.1)): dependencies: @@ -17626,21 +20802,21 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-prefer-arrow-functions@3.9.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + eslint-plugin-prefer-arrow-functions@3.9.1(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2): dependencies: '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2) eslint: 9.39.2(jiti@2.6.1) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.7.4): + eslint-plugin-prettier@5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.1): dependencies: eslint: 9.39.2(jiti@2.6.1) - prettier: 3.7.4 - prettier-linter-helpers: 1.0.0 - synckit: 0.11.11 + prettier: 3.8.1 + prettier-linter-helpers: 1.0.1 + synckit: 0.11.12 optionalDependencies: '@types/eslint': 9.6.1 eslint-config-prettier: 10.1.8(eslint@9.39.2(jiti@2.6.1)) @@ -17660,7 +20836,7 @@ snapshots: dependencies: eslint: 9.39.2(jiti@2.6.1) - eslint-plugin-react-refresh@0.4.25(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-react-refresh@0.5.2(eslint@9.39.2(jiti@2.6.1)): dependencies: eslint: 9.39.2(jiti@2.6.1) @@ -17694,32 +20870,30 @@ snapshots: dependencies: eslint: 9.39.2(jiti@2.6.1) - eslint-plugin-turbo@2.6.3(eslint@9.39.2(jiti@2.6.1))(turbo@2.6.3): + eslint-plugin-turbo@2.9.4(eslint@9.39.2(jiti@2.6.1))(turbo@2.9.4): dependencies: dotenv: 16.0.3 eslint: 9.39.2(jiti@2.6.1) - turbo: 2.6.3 + turbo: 2.9.4 - eslint-plugin-unicorn@62.0.0(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-unicorn@64.0.0(eslint@9.39.2(jiti@2.6.1)): dependencies: '@babel/helper-validator-identifier': 7.28.5 - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1)) - '@eslint/plugin-kit': 0.4.1 + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) change-case: 5.4.4 - ci-info: 4.3.1 + ci-info: 4.4.0 clean-regexp: 1.0.0 - core-js-compat: 3.47.0 + core-js-compat: 3.49.0 eslint: 9.39.2(jiti@2.6.1) - esquery: 1.6.0 find-up-simple: 1.0.1 - globals: 16.5.0 + globals: 17.4.0 indent-string: 5.0.0 is-builtin-module: 5.0.0 jsesc: 3.1.0 pluralize: 8.0.0 regexp-tree: 0.1.27 regjsparser: 0.13.0 - semver: 7.7.3 + semver: 7.7.4 strip-indent: 4.1.1 eslint-scope@5.1.1: @@ -17736,6 +20910,8 @@ snapshots: eslint-visitor-keys@4.2.1: {} + eslint-visitor-keys@5.0.1: {} + eslint@9.39.2(jiti@2.6.1): dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1)) @@ -17846,7 +21022,7 @@ snapshots: eval@0.1.8: dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 require-like: 0.1.2 event-target-shim@5.0.1: {} @@ -17855,6 +21031,8 @@ snapshots: eventemitter3@5.0.1: {} + events-intercept@2.0.0: {} + events-universal@1.0.1: dependencies: bare-events: 2.8.2 @@ -17871,6 +21049,19 @@ snapshots: dependencies: eventsource-parser: 3.0.6 + execa@3.2.0: + dependencies: + cross-spawn: 7.0.6 + get-stream: 5.2.0 + human-signals: 1.1.1 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + p-finally: 2.0.1 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -17902,6 +21093,10 @@ snapshots: exit-x@0.2.2: {} + expand-tilde@2.0.2: + dependencies: + homedir-polyfill: 1.0.3 + expect@30.2.0: dependencies: '@jest/expect-utils': 30.2.0 @@ -17911,11 +21106,21 @@ snapshots: jest-mock: 30.2.0 jest-util: 30.2.0 + expect@30.3.0: + dependencies: + '@jest/expect-utils': 30.3.0 + '@jest/get-type': 30.1.0 + jest-matcher-utils: 30.3.0 + jest-message-util: 30.3.0 + jest-mock: 30.3.0 + jest-util: 30.3.0 + exponential-backoff@3.1.3: {} - express-rate-limit@7.5.1(express@5.2.1): + express-rate-limit@8.3.2(express@5.2.1): dependencies: express: 5.2.1 + ip-address: 10.1.0 express@4.22.1: dependencies: @@ -17940,7 +21145,7 @@ snapshots: parseurl: 1.3.3 path-to-regexp: 0.1.12 proxy-addr: 2.0.7 - qs: 6.14.0 + qs: 6.14.1 range-parser: 1.2.1 safe-buffer: 5.2.1 send: 0.19.2 @@ -17992,10 +21197,6 @@ snapshots: extend@3.0.2: {} - fast-check@3.23.2: - dependencies: - pure-rand: 6.1.0 - fast-content-type-parse@3.0.0: {} fast-deep-equal@3.1.3: {} @@ -18036,9 +21237,19 @@ snapshots: fast-uri@3.1.0: {} - fast-xml-parser@4.5.3: + fast-wrap-ansi@0.2.0: dependencies: - strnum: 1.1.2 + fast-string-width: 3.0.2 + + fast-xml-builder@1.1.4: + dependencies: + path-expression-matcher: 1.5.0 + + fast-xml-parser@5.5.8: + dependencies: + fast-xml-builder: 1.1.4 + path-expression-matcher: 1.5.0 + strnum: 2.2.3 fastq@1.19.1: dependencies: @@ -18074,6 +21285,10 @@ snapshots: dependencies: walk-up-path: 4.0.0 + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -18094,16 +21309,18 @@ snapshots: dependencies: flat-cache: 4.0.1 - file-loader@6.2.0(webpack@5.104.1): + file-loader@6.2.0(webpack@5.104.1(@swc/core@1.15.24)): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) file-saver@2.0.5: {} file-type@3.9.0: {} + file-uri-to-path@1.0.0: {} + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -18153,11 +21370,12 @@ snapshots: locate-path: 7.2.0 path-exists: 5.0.0 - find-up@7.0.0: + findup-sync@5.0.0: dependencies: - locate-path: 7.2.0 - path-exists: 5.0.0 - unicorn-magic: 0.1.0 + detect-file: 1.0.0 + is-glob: 4.0.3 + micromatch: 4.0.8 + resolve-dir: 1.0.1 fix-dts-default-cjs-exports@1.0.1: dependencies: @@ -18189,15 +21407,6 @@ snapshots: form-data-encoder@2.1.4: {} - form-data@2.5.5: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.2 - mime-types: 2.1.35 - safe-buffer: 5.2.1 - form-data@4.0.5: dependencies: asynckit: 0.4.0 @@ -18230,19 +21439,28 @@ snapshots: jsonfile: 6.2.0 universalify: 2.0.1 - fs-extra@11.3.2: + fs-extra@11.1.0: dependencies: graceful-fs: 4.2.11 jsonfile: 6.2.0 universalify: 2.0.1 - fs-extra@9.1.0: + fs-extra@11.3.4: dependencies: - at-least-node: 1.0.0 graceful-fs: 4.2.11 jsonfile: 6.2.0 universalify: 2.0.1 + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-minipass@1.2.7: + dependencies: + minipass: 2.9.0 + fs-minipass@3.0.3: dependencies: minipass: 7.1.2 @@ -18265,28 +21483,21 @@ snapshots: functions-have-names@1.2.3: {} - gaxios@6.7.1(encoding@0.1.13): - dependencies: - extend: 3.0.2 - https-proxy-agent: 7.0.6 - is-stream: 2.0.1 - node-fetch: 2.7.0(encoding@0.1.13) - uuid: 9.0.1 - transitivePeerDependencies: - - encoding - - supports-color - - gcp-metadata@6.1.1(encoding@0.1.13): + gauge@5.0.2: dependencies: - gaxios: 6.7.1(encoding@0.1.13) - google-logging-utils: 0.0.2 - json-bigint: 1.0.0 - transitivePeerDependencies: - - encoding - - supports-color + aproba: 2.1.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + signal-exit: 4.1.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 generator-function@2.0.1: {} + generic-pool@3.4.2: {} + gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -18322,6 +21533,10 @@ snapshots: data-uri-to-buffer: 2.0.2 source-map: 0.6.1 + get-stream@5.2.0: + dependencies: + pump: 3.0.3 + get-stream@6.0.1: {} get-stream@9.0.1: @@ -18339,11 +21554,13 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - git-raw-commits@4.0.0: + git-raw-commits@5.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0): dependencies: - dargs: 8.1.0 - meow: 12.1.1 - split2: 4.2.0 + '@conventional-changelog/git-client': 2.6.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0) + meow: 13.2.0 + transitivePeerDependencies: + - conventional-commits-filter + - conventional-commits-parser git-up@8.1.1: dependencies: @@ -18377,7 +21594,16 @@ snapshots: minimatch: 9.0.5 minipass: 7.1.2 package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 + path-scurry: 1.11.1 + + glob@11.1.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.2.3 + minimatch: 10.2.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.1 glob@13.0.0: dependencies: @@ -18390,7 +21616,7 @@ snapshots: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.1.2 + minimatch: 3.1.5 once: 1.4.0 path-is-absolute: 1.0.1 @@ -18411,9 +21637,23 @@ snapshots: dependencies: ini: 2.0.0 + global-modules@1.0.0: + dependencies: + global-prefix: 1.0.2 + is-windows: 1.0.2 + resolve-dir: 1.0.1 + + global-prefix@1.0.2: + dependencies: + expand-tilde: 2.0.2 + homedir-polyfill: 1.0.3 + ini: 1.3.8 + is-windows: 1.0.2 + which: 1.3.1 + globals@14.0.0: {} - globals@16.5.0: {} + globals@17.4.0: {} globalthis@1.0.4: dependencies: @@ -18437,29 +21677,6 @@ snapshots: merge2: 1.4.1 slash: 4.0.0 - globby@14.1.0: - dependencies: - '@sindresorhus/merge-streams': 2.3.0 - fast-glob: 3.3.3 - ignore: 7.0.5 - path-type: 6.0.0 - slash: 5.1.0 - unicorn-magic: 0.3.0 - - google-auth-library@9.15.1(encoding@0.1.13): - dependencies: - base64-js: 1.5.1 - ecdsa-sig-formatter: 1.0.11 - gaxios: 6.7.1(encoding@0.1.13) - gcp-metadata: 6.1.1(encoding@0.1.13) - gtoken: 7.1.0(encoding@0.1.13) - jws: 4.0.1 - transitivePeerDependencies: - - encoding - - supports-color - - google-logging-utils@0.0.2: {} - gopd@1.2.0: {} got@12.6.1: @@ -18497,14 +21714,6 @@ snapshots: section-matter: 1.0.0 strip-bom-string: 1.0.0 - gtoken@7.1.0(encoding@0.1.13): - dependencies: - gaxios: 6.7.1(encoding@0.1.13) - jws: 4.0.1 - transitivePeerDependencies: - - encoding - - supports-color - gzip-size@6.0.0: dependencies: duplexer: 0.1.2 @@ -18548,16 +21757,6 @@ snapshots: dependencies: function-bind: 1.1.2 - hast-util-from-parse5@7.1.2: - dependencies: - '@types/hast': 2.3.10 - '@types/unist': 2.0.11 - hastscript: 7.2.0 - property-information: 6.5.0 - vfile: 5.3.7 - vfile-location: 4.1.0 - web-namespaces: 2.0.1 - hast-util-from-parse5@8.0.3: dependencies: '@types/hast': 3.0.4 @@ -18569,28 +21768,10 @@ snapshots: vfile-location: 5.0.3 web-namespaces: 2.0.1 - hast-util-parse-selector@3.1.1: - dependencies: - '@types/hast': 2.3.10 - hast-util-parse-selector@4.0.0: dependencies: '@types/hast': 3.0.4 - hast-util-raw@7.2.3: - dependencies: - '@types/hast': 2.3.10 - '@types/parse5': 6.0.3 - hast-util-from-parse5: 7.1.2 - hast-util-to-parse5: 7.1.0 - html-void-elements: 2.0.1 - parse5: 6.0.1 - unist-util-position: 4.0.4 - unist-util-visit: 4.1.2 - vfile: 5.3.7 - web-namespaces: 2.0.1 - zwitch: 2.0.4 - hast-util-raw@9.1.0: dependencies: '@types/hast': 3.0.4 @@ -18648,15 +21829,6 @@ snapshots: transitivePeerDependencies: - supports-color - hast-util-to-parse5@7.1.0: - dependencies: - '@types/hast': 2.3.10 - comma-separated-tokens: 2.0.3 - property-information: 6.5.0 - space-separated-tokens: 2.0.2 - web-namespaces: 2.0.1 - zwitch: 2.0.4 - hast-util-to-parse5@8.0.1: dependencies: '@types/hast': 3.0.4 @@ -18667,20 +21839,10 @@ snapshots: web-namespaces: 2.0.1 zwitch: 2.0.4 - hast-util-whitespace@2.0.1: {} - hast-util-whitespace@3.0.0: dependencies: '@types/hast': 3.0.4 - hastscript@7.2.0: - dependencies: - '@types/hast': 2.3.10 - comma-separated-tokens: 2.0.3 - hast-util-parse-selector: 3.1.1 - property-information: 6.5.0 - space-separated-tokens: 2.0.2 - hastscript@9.0.1: dependencies: '@types/hast': 3.0.4 @@ -18710,9 +21872,11 @@ snapshots: dependencies: react-is: 16.13.1 - hosted-git-info@7.0.2: + homedir-polyfill@1.0.3: dependencies: - lru-cache: 10.4.3 + parse-passwd: 1.0.0 + + hono@4.12.12: {} hosted-git-info@8.1.0: dependencies: @@ -18735,8 +21899,6 @@ snapshots: dependencies: whatwg-encoding: 3.1.1 - html-entities@2.6.0: {} - html-escaper@2.0.2: {} html-minifier-terser@6.1.0: @@ -18761,19 +21923,20 @@ snapshots: html-tags@3.3.1: {} - html-void-elements@2.0.1: {} + html-url-attributes@3.0.1: {} html-void-elements@3.0.0: {} - html-webpack-plugin@5.6.5(webpack@5.104.1): + html-webpack-plugin@5.6.5(@rspack/core@1.7.11)(webpack@5.104.1(@swc/core@1.15.24)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 - lodash: 4.17.21 + lodash: 4.17.23 pretty-error: 4.0.0 tapable: 2.3.0 optionalDependencies: - webpack: 5.104.1 + '@rspack/core': 1.7.11 + webpack: 5.104.1(@swc/core@1.15.24) htmlparser2@6.1.0: dependencies: @@ -18798,6 +21961,11 @@ snapshots: http-deceiver@1.2.7: {} + http-errors@1.4.0: + dependencies: + inherits: 2.0.1 + statuses: 1.5.0 + http-errors@1.6.3: dependencies: depd: 1.1.2 @@ -18805,6 +21973,14 @@ snapshots: setprototypeof: 1.1.0 statuses: 1.5.0 + http-errors@1.7.3: + dependencies: + depd: 1.1.2 + inherits: 2.0.4 + setprototypeof: 1.1.1 + statuses: 1.5.0 + toidentifier: 1.0.0 + http-errors@1.8.1: dependencies: depd: 1.1.2 @@ -18823,14 +21999,6 @@ snapshots: http-parser-js@0.5.10: {} - http-proxy-agent@5.0.0: - dependencies: - '@tootallnate/once': 2.0.0 - agent-base: 6.0.2 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 @@ -18867,13 +22035,6 @@ snapshots: quick-lru: 5.1.1 resolve-alpn: 1.2.1 - https-proxy-agent@5.0.1: - dependencies: - agent-base: 6.0.2 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 @@ -18881,6 +22042,8 @@ snapshots: transitivePeerDependencies: - supports-color + human-signals@1.1.1: {} + human-signals@2.1.0: {} human-signals@8.0.1: {} @@ -18919,6 +22082,8 @@ snapshots: image-size@2.0.2: {} + immer@11.1.4: {} + immer@9.0.21: {} immutable@5.1.4: {} @@ -18937,14 +22102,16 @@ snapshots: import-meta-resolve@4.2.0: {} + import-sync@2.2.3: + dependencies: + '@httptoolkit/esm': 3.3.2 + imurmurhash@0.1.4: {} indent-string@4.0.0: {} indent-string@5.0.0: {} - index-to-position@1.2.0: {} - infima@0.2.0-alpha.45: {} inflation@2.1.0: {} @@ -18956,6 +22123,8 @@ snapshots: once: 1.4.0 wrappy: 1.0.2 + inherits@2.0.1: {} + inherits@2.0.3: {} inherits@2.0.4: {} @@ -18968,8 +22137,6 @@ snapshots: ini@6.0.0: {} - inline-style-parser@0.1.1: {} - inline-style-parser@0.2.7: {} internal-slot@1.1.0: @@ -19031,15 +22198,13 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-buffer@2.0.5: {} - is-builtin-module@5.0.0: dependencies: builtin-modules: 5.0.0 is-bun-module@2.0.0: dependencies: - semver: 7.7.3 + semver: 7.7.4 is-callable@1.2.7: {} @@ -19107,8 +22272,6 @@ snapshots: global-dirs: 3.0.1 is-path-inside: 3.0.3 - is-interactive@2.0.0: {} - is-map@2.0.3: {} is-negative-zero@2.0.3: {} @@ -19180,18 +22343,12 @@ snapshots: has-symbols: 1.1.0 safe-regex-test: 1.1.0 - is-text-path@2.0.0: - dependencies: - text-extensions: 2.4.0 - is-typed-array@1.1.15: dependencies: which-typed-array: 1.1.19 is-typedarray@1.0.0: {} - is-unicode-supported@1.3.0: {} - is-unicode-supported@2.1.0: {} is-weakmap@2.0.2: {} @@ -19205,6 +22362,8 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 + is-windows@1.0.2: {} + is-wsl@2.2.0: dependencies: is-docker: 2.2.1 @@ -19235,7 +22394,7 @@ snapshots: '@babel/parser': 7.28.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.7.3 + semver: 7.7.4 transitivePeerDependencies: - supports-color @@ -19273,31 +22432,35 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jest-changed-files@30.2.0: + jackspeak@4.2.3: + dependencies: + '@isaacs/cliui': 9.0.0 + + jest-changed-files@30.3.0: dependencies: execa: 5.1.1 - jest-util: 30.2.0 + jest-util: 30.3.0 p-limit: 3.1.0 - jest-circus@30.2.0: + jest-circus@30.3.0: dependencies: - '@jest/environment': 30.2.0 - '@jest/expect': 30.2.0 - '@jest/test-result': 30.2.0 - '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@jest/environment': 30.3.0 + '@jest/expect': 30.3.0 + '@jest/test-result': 30.3.0 + '@jest/types': 30.3.0 + '@types/node': 25.5.2 chalk: 4.1.2 co: 4.6.0 dedent: 1.7.0 is-generator-fn: 2.1.0 - jest-each: 30.2.0 - jest-matcher-utils: 30.2.0 - jest-message-util: 30.2.0 - jest-runtime: 30.2.0 - jest-snapshot: 30.2.0 - jest-util: 30.2.0 + jest-each: 30.3.0 + jest-matcher-utils: 30.3.0 + jest-message-util: 30.3.0 + jest-runtime: 30.3.0 + jest-snapshot: 30.3.0 + jest-util: 30.3.0 p-limit: 3.1.0 - pretty-format: 30.2.0 + pretty-format: 30.3.0 pure-rand: 7.0.1 slash: 3.0.0 stack-utils: 2.0.6 @@ -19305,17 +22468,17 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)): + jest-cli@30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2)): dependencies: - '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) - '@jest/test-result': 30.2.0 - '@jest/types': 30.2.0 + '@jest/core': 30.3.0(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2)) + '@jest/test-result': 30.3.0 + '@jest/types': 30.3.0 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) - jest-util: 30.2.0 - jest-validate: 30.2.0 + jest-config: 30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2)) + jest-util: 30.3.0 + jest-validate: 30.3.0 yargs: 17.7.2 transitivePeerDependencies: - '@types/node' @@ -19324,35 +22487,34 @@ snapshots: - supports-color - ts-node - jest-config@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)): + jest-config@30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2)): dependencies: '@babel/core': 7.28.5 '@jest/get-type': 30.1.0 '@jest/pattern': 30.0.1 - '@jest/test-sequencer': 30.2.0 - '@jest/types': 30.2.0 - babel-jest: 30.2.0(@babel/core@7.28.5) + '@jest/test-sequencer': 30.3.0 + '@jest/types': 30.3.0 + babel-jest: 30.3.0(@babel/core@7.28.5) chalk: 4.1.2 ci-info: 4.3.1 deepmerge: 4.3.1 glob: 10.5.0 graceful-fs: 4.2.11 - jest-circus: 30.2.0 + jest-circus: 30.3.0 jest-docblock: 30.2.0 - jest-environment-node: 30.2.0 + jest-environment-node: 30.3.0 jest-regex-util: 30.0.1 - jest-resolve: 30.2.0 - jest-runner: 30.2.0 - jest-util: 30.2.0 - jest-validate: 30.2.0 - micromatch: 4.0.8 + jest-resolve: 30.3.0 + jest-runner: 30.3.0 + jest-util: 30.3.0 + jest-validate: 30.3.0 parse-json: 5.2.0 - pretty-format: 30.2.0 + pretty-format: 30.3.0 slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 25.0.3 - ts-node: 10.9.2(@types/node@25.0.3)(typescript@5.9.3) + '@types/node': 25.5.2 + ts-node: 10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -19364,59 +22526,66 @@ snapshots: chalk: 4.1.2 pretty-format: 30.2.0 + jest-diff@30.3.0: + dependencies: + '@jest/diff-sequences': 30.3.0 + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + pretty-format: 30.3.0 + jest-docblock@30.2.0: dependencies: detect-newline: 3.1.0 - jest-each@30.2.0: + jest-each@30.3.0: dependencies: '@jest/get-type': 30.1.0 - '@jest/types': 30.2.0 + '@jest/types': 30.3.0 chalk: 4.1.2 - jest-util: 30.2.0 - pretty-format: 30.2.0 + jest-util: 30.3.0 + pretty-format: 30.3.0 jest-environment-jsdom@30.2.0: dependencies: '@jest/environment': 30.2.0 '@jest/environment-jsdom-abstract': 30.2.0(jsdom@26.1.0) '@types/jsdom': 21.1.7 - '@types/node': 25.0.3 + '@types/node': 25.5.2 jsdom: 26.1.0 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - jest-environment-node@30.2.0: + jest-environment-node@30.3.0: dependencies: - '@jest/environment': 30.2.0 - '@jest/fake-timers': 30.2.0 - '@jest/types': 30.2.0 - '@types/node': 25.0.3 - jest-mock: 30.2.0 - jest-util: 30.2.0 - jest-validate: 30.2.0 + '@jest/environment': 30.3.0 + '@jest/fake-timers': 30.3.0 + '@jest/types': 30.3.0 + '@types/node': 25.5.2 + jest-mock: 30.3.0 + jest-util: 30.3.0 + jest-validate: 30.3.0 - jest-haste-map@30.2.0: + jest-haste-map@30.3.0: dependencies: - '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@jest/types': 30.3.0 + '@types/node': 25.5.2 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 jest-regex-util: 30.0.1 - jest-util: 30.2.0 - jest-worker: 30.2.0 - micromatch: 4.0.8 + jest-util: 30.3.0 + jest-worker: 30.3.0 + picomatch: 4.0.3 walker: 1.0.8 optionalDependencies: fsevents: 2.3.3 - jest-leak-detector@30.2.0: + jest-leak-detector@30.3.0: dependencies: '@jest/get-type': 30.1.0 - pretty-format: 30.2.0 + pretty-format: 30.3.0 jest-matcher-utils@30.2.0: dependencies: @@ -19425,6 +22594,13 @@ snapshots: jest-diff: 30.2.0 pretty-format: 30.2.0 + jest-matcher-utils@30.3.0: + dependencies: + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + jest-diff: 30.3.0 + pretty-format: 30.3.0 + jest-message-util@30.2.0: dependencies: '@babel/code-frame': 7.27.1 @@ -19437,111 +22613,129 @@ snapshots: slash: 3.0.0 stack-utils: 2.0.6 + jest-message-util@30.3.0: + dependencies: + '@babel/code-frame': 7.27.1 + '@jest/types': 30.3.0 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + picomatch: 4.0.3 + pretty-format: 30.3.0 + slash: 3.0.0 + stack-utils: 2.0.6 + jest-mock@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 25.5.2 jest-util: 30.2.0 - jest-pnp-resolver@1.2.3(jest-resolve@30.2.0): + jest-mock@30.3.0: + dependencies: + '@jest/types': 30.3.0 + '@types/node': 25.5.2 + jest-util: 30.3.0 + + jest-pnp-resolver@1.2.3(jest-resolve@30.3.0): optionalDependencies: - jest-resolve: 30.2.0 + jest-resolve: 30.3.0 jest-regex-util@30.0.1: {} - jest-resolve-dependencies@30.2.0: + jest-resolve-dependencies@30.3.0: dependencies: jest-regex-util: 30.0.1 - jest-snapshot: 30.2.0 + jest-snapshot: 30.3.0 transitivePeerDependencies: - supports-color - jest-resolve@30.2.0: + jest-resolve@30.3.0: dependencies: chalk: 4.1.2 graceful-fs: 4.2.11 - jest-haste-map: 30.2.0 - jest-pnp-resolver: 1.2.3(jest-resolve@30.2.0) - jest-util: 30.2.0 - jest-validate: 30.2.0 + jest-haste-map: 30.3.0 + jest-pnp-resolver: 1.2.3(jest-resolve@30.3.0) + jest-util: 30.3.0 + jest-validate: 30.3.0 slash: 3.0.0 unrs-resolver: 1.11.1 - jest-runner@30.2.0: + jest-runner@30.3.0: dependencies: - '@jest/console': 30.2.0 - '@jest/environment': 30.2.0 - '@jest/test-result': 30.2.0 - '@jest/transform': 30.2.0 - '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@jest/console': 30.3.0 + '@jest/environment': 30.3.0 + '@jest/test-result': 30.3.0 + '@jest/transform': 30.3.0 + '@jest/types': 30.3.0 + '@types/node': 25.5.2 chalk: 4.1.2 emittery: 0.13.1 exit-x: 0.2.2 graceful-fs: 4.2.11 jest-docblock: 30.2.0 - jest-environment-node: 30.2.0 - jest-haste-map: 30.2.0 - jest-leak-detector: 30.2.0 - jest-message-util: 30.2.0 - jest-resolve: 30.2.0 - jest-runtime: 30.2.0 - jest-util: 30.2.0 - jest-watcher: 30.2.0 - jest-worker: 30.2.0 + jest-environment-node: 30.3.0 + jest-haste-map: 30.3.0 + jest-leak-detector: 30.3.0 + jest-message-util: 30.3.0 + jest-resolve: 30.3.0 + jest-runtime: 30.3.0 + jest-util: 30.3.0 + jest-watcher: 30.3.0 + jest-worker: 30.3.0 p-limit: 3.1.0 source-map-support: 0.5.13 transitivePeerDependencies: - supports-color - jest-runtime@30.2.0: + jest-runtime@30.3.0: dependencies: - '@jest/environment': 30.2.0 - '@jest/fake-timers': 30.2.0 - '@jest/globals': 30.2.0 + '@jest/environment': 30.3.0 + '@jest/fake-timers': 30.3.0 + '@jest/globals': 30.3.0 '@jest/source-map': 30.0.1 - '@jest/test-result': 30.2.0 - '@jest/transform': 30.2.0 - '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@jest/test-result': 30.3.0 + '@jest/transform': 30.3.0 + '@jest/types': 30.3.0 + '@types/node': 25.5.2 chalk: 4.1.2 cjs-module-lexer: 2.1.1 collect-v8-coverage: 1.0.3 glob: 10.5.0 graceful-fs: 4.2.11 - jest-haste-map: 30.2.0 - jest-message-util: 30.2.0 - jest-mock: 30.2.0 + jest-haste-map: 30.3.0 + jest-message-util: 30.3.0 + jest-mock: 30.3.0 jest-regex-util: 30.0.1 - jest-resolve: 30.2.0 - jest-snapshot: 30.2.0 - jest-util: 30.2.0 + jest-resolve: 30.3.0 + jest-snapshot: 30.3.0 + jest-util: 30.3.0 slash: 3.0.0 strip-bom: 4.0.0 transitivePeerDependencies: - supports-color - jest-snapshot@30.2.0: + jest-snapshot@30.3.0: dependencies: '@babel/core': 7.28.5 '@babel/generator': 7.28.5 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) '@babel/types': 7.28.5 - '@jest/expect-utils': 30.2.0 + '@jest/expect-utils': 30.3.0 '@jest/get-type': 30.1.0 - '@jest/snapshot-utils': 30.2.0 - '@jest/transform': 30.2.0 - '@jest/types': 30.2.0 + '@jest/snapshot-utils': 30.3.0 + '@jest/transform': 30.3.0 + '@jest/types': 30.3.0 babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.5) chalk: 4.1.2 - expect: 30.2.0 + expect: 30.3.0 graceful-fs: 4.2.11 - jest-diff: 30.2.0 - jest-matcher-utils: 30.2.0 - jest-message-util: 30.2.0 - jest-util: 30.2.0 - pretty-format: 30.2.0 + jest-diff: 30.3.0 + jest-matcher-utils: 30.3.0 + jest-message-util: 30.3.0 + jest-util: 30.3.0 + pretty-format: 30.3.0 semver: 7.7.3 synckit: 0.11.11 transitivePeerDependencies: @@ -19550,7 +22744,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 25.0.3 + '@types/node': 25.5.2 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -19559,59 +22753,68 @@ snapshots: jest-util@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 25.5.2 + chalk: 4.1.2 + ci-info: 4.3.1 + graceful-fs: 4.2.11 + picomatch: 4.0.3 + + jest-util@30.3.0: + dependencies: + '@jest/types': 30.3.0 + '@types/node': 25.5.2 chalk: 4.1.2 ci-info: 4.3.1 graceful-fs: 4.2.11 picomatch: 4.0.3 - jest-validate@30.2.0: + jest-validate@30.3.0: dependencies: '@jest/get-type': 30.1.0 - '@jest/types': 30.2.0 + '@jest/types': 30.3.0 camelcase: 6.3.0 chalk: 4.1.2 leven: 3.1.0 - pretty-format: 30.2.0 + pretty-format: 30.3.0 - jest-watcher@30.2.0: + jest-watcher@30.3.0: dependencies: - '@jest/test-result': 30.2.0 - '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@jest/test-result': 30.3.0 + '@jest/types': 30.3.0 + '@types/node': 25.5.2 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 - jest-util: 30.2.0 + jest-util: 30.3.0 string-length: 4.0.2 jest-worker@27.5.1: dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@29.7.0: dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest-worker@30.2.0: + jest-worker@30.3.0: dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@ungap/structured-clone': 1.3.0 - jest-util: 30.2.0 + jest-util: 30.3.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)): + jest@30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2)): dependencies: - '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) - '@jest/types': 30.2.0 + '@jest/core': 30.3.0(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2)) + '@jest/types': 30.3.0 import-local: 3.2.0 - jest-cli: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) + jest-cli: 30.3.0(@types/node@25.5.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -19685,10 +22888,6 @@ snapshots: jsesc@3.1.0: {} - json-bigint@1.0.0: - dependencies: - bignumber.js: 9.3.1 - json-buffer@3.0.1: {} json-crawl@0.5.3: {} @@ -19711,10 +22910,23 @@ snapshots: json-schema-compare: 0.2.2 lodash: 4.17.21 + json-schema-to-ts@1.6.4: + dependencies: + '@types/json-schema': 7.0.15 + ts-toolbelt: 6.15.5 + + json-schema-to-ts@2.7.2: + dependencies: + '@babel/runtime': 7.28.4 + '@types/json-schema': 7.0.15 + ts-algebra: 1.2.2 + json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} + json-schema-typed@8.0.2: {} + json-schema@0.4.0: {} json-stable-stringify-without-jsonify@1.0.1: {} @@ -19735,7 +22947,9 @@ snapshots: jsonc-parser@2.2.1: {} - jsonc-parser@3.3.1: {} + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 jsonfile@6.2.0: dependencies: @@ -19745,8 +22959,6 @@ snapshots: jsonify@0.0.1: {} - jsonparse@1.3.1: {} - jsonpath-plus@10.3.0: dependencies: '@jsep-plugin/assignment': 1.3.0(jsep@1.4.0) @@ -19755,6 +22967,19 @@ snapshots: jsonpointer@5.0.1: {} + jsonwebtoken@9.0.3: + dependencies: + jws: 4.0.1 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.7.4 + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -19785,10 +23010,23 @@ snapshots: kleur@3.0.3: {} - kleur@4.1.5: {} - koa-compose@4.1.0: {} + koa-send@5.0.1: + dependencies: + debug: 4.4.3 + http-errors: 1.8.1 + resolve-path: 1.4.0 + transitivePeerDependencies: + - supports-color + + koa-static@5.0.0: + dependencies: + debug: 3.2.7 + koa-send: 5.0.1 + transitivePeerDependencies: + - supports-color + koa@3.1.1: dependencies: accepts: 1.3.8 @@ -19836,18 +23074,66 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} - lint-staged@16.2.7: + lint-staged@16.4.0: dependencies: - commander: 14.0.2 + commander: 14.0.3 listr2: 9.0.5 - micromatch: 4.0.8 - nano-spawn: 2.0.0 - pidtree: 0.6.0 + picomatch: 4.0.3 string-argv: 0.3.2 + tinyexec: 1.1.1 yaml: 2.8.2 liquid-json@0.3.1: {} @@ -19889,8 +23175,18 @@ snapshots: lodash.debounce@4.0.8: {} + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + lodash.isplainobject@4.0.6: {} + lodash.isstring@4.0.1: {} + lodash.kebabcase@4.1.1: {} lodash.memoize@4.1.2: {} @@ -19899,6 +23195,8 @@ snapshots: lodash.mergewith@4.6.2: {} + lodash.once@4.1.1: {} + lodash.snakecase@4.1.1: {} lodash.startcase@4.4.0: {} @@ -19911,10 +23209,7 @@ snapshots: lodash@4.17.21: {} - log-symbols@6.0.0: - dependencies: - chalk: 5.6.2 - is-unicode-supported: 1.3.0 + lodash@4.17.23: {} log-update@6.1.0: dependencies: @@ -19946,6 +23241,10 @@ snapshots: dependencies: yallist: 3.1.1 + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + lz-string@1.5.0: {} magic-string@0.25.9: @@ -19958,10 +23257,9 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.3 + semver: 7.7.4 - make-error@1.3.6: - optional: true + make-error@1.3.6: {} make-fetch-happen@15.0.3: dependencies: @@ -19997,12 +23295,6 @@ snapshots: math-intrinsics@1.1.0: {} - mdast-util-definitions@5.1.2: - dependencies: - '@types/mdast': 3.0.15 - '@types/unist': 2.0.11 - unist-util-visit: 4.1.2 - mdast-util-directive@3.1.0: dependencies: '@types/mdast': 4.0.4 @@ -20017,13 +23309,6 @@ snapshots: transitivePeerDependencies: - supports-color - mdast-util-find-and-replace@2.2.2: - dependencies: - '@types/mdast': 3.0.15 - escape-string-regexp: 5.0.0 - unist-util-is: 5.2.1 - unist-util-visit-parents: 5.1.3 - mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -20031,23 +23316,6 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 - mdast-util-from-markdown@1.3.1: - dependencies: - '@types/mdast': 3.0.15 - '@types/unist': 2.0.11 - decode-named-character-reference: 1.2.0 - mdast-util-to-string: 3.2.0 - micromark: 3.2.0 - micromark-util-decode-numeric-character-reference: 1.1.0 - micromark-util-decode-string: 1.1.0 - micromark-util-normalize-identifier: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - unist-util-stringify-position: 3.0.3 - uvu: 0.5.6 - transitivePeerDependencies: - - supports-color - mdast-util-from-markdown@2.0.2: dependencies: '@types/mdast': 4.0.4 @@ -20074,14 +23342,7 @@ snapshots: mdast-util-to-markdown: 2.1.2 micromark-extension-frontmatter: 2.0.0 transitivePeerDependencies: - - supports-color - - mdast-util-gfm-autolink-literal@1.0.3: - dependencies: - '@types/mdast': 3.0.15 - ccount: 2.0.1 - mdast-util-find-and-replace: 2.2.2 - micromark-util-character: 1.2.0 + - supports-color mdast-util-gfm-autolink-literal@2.0.1: dependencies: @@ -20091,12 +23352,6 @@ snapshots: mdast-util-find-and-replace: 3.0.2 micromark-util-character: 2.1.1 - mdast-util-gfm-footnote@1.0.2: - dependencies: - '@types/mdast': 3.0.15 - mdast-util-to-markdown: 1.5.0 - micromark-util-normalize-identifier: 1.1.0 - mdast-util-gfm-footnote@2.1.0: dependencies: '@types/mdast': 4.0.4 @@ -20107,11 +23362,6 @@ snapshots: transitivePeerDependencies: - supports-color - mdast-util-gfm-strikethrough@1.0.3: - dependencies: - '@types/mdast': 3.0.15 - mdast-util-to-markdown: 1.5.0 - mdast-util-gfm-strikethrough@2.0.0: dependencies: '@types/mdast': 4.0.4 @@ -20120,15 +23370,6 @@ snapshots: transitivePeerDependencies: - supports-color - mdast-util-gfm-table@1.0.7: - dependencies: - '@types/mdast': 3.0.15 - markdown-table: 3.0.4 - mdast-util-from-markdown: 1.3.1 - mdast-util-to-markdown: 1.5.0 - transitivePeerDependencies: - - supports-color - mdast-util-gfm-table@2.0.0: dependencies: '@types/mdast': 4.0.4 @@ -20139,11 +23380,6 @@ snapshots: transitivePeerDependencies: - supports-color - mdast-util-gfm-task-list-item@1.0.2: - dependencies: - '@types/mdast': 3.0.15 - mdast-util-to-markdown: 1.5.0 - mdast-util-gfm-task-list-item@2.0.0: dependencies: '@types/mdast': 4.0.4 @@ -20153,18 +23389,6 @@ snapshots: transitivePeerDependencies: - supports-color - mdast-util-gfm@2.0.2: - dependencies: - mdast-util-from-markdown: 1.3.1 - mdast-util-gfm-autolink-literal: 1.0.3 - mdast-util-gfm-footnote: 1.0.2 - mdast-util-gfm-strikethrough: 1.0.3 - mdast-util-gfm-table: 1.0.7 - mdast-util-gfm-task-list-item: 1.0.2 - mdast-util-to-markdown: 1.5.0 - transitivePeerDependencies: - - supports-color - mdast-util-gfm@3.1.0: dependencies: mdast-util-from-markdown: 2.0.2 @@ -20226,27 +23450,11 @@ snapshots: transitivePeerDependencies: - supports-color - mdast-util-phrasing@3.0.1: - dependencies: - '@types/mdast': 3.0.15 - unist-util-is: 5.2.1 - mdast-util-phrasing@4.1.0: dependencies: '@types/mdast': 4.0.4 unist-util-is: 6.0.1 - mdast-util-to-hast@12.3.0: - dependencies: - '@types/hast': 2.3.10 - '@types/mdast': 3.0.15 - mdast-util-definitions: 5.1.2 - micromark-util-sanitize-uri: 1.2.0 - trim-lines: 3.0.1 - unist-util-generated: 2.0.1 - unist-util-position: 4.0.4 - unist-util-visit: 4.1.2 - mdast-util-to-hast@13.2.1: dependencies: '@types/hast': 3.0.4 @@ -20259,17 +23467,6 @@ snapshots: unist-util-visit: 5.0.0 vfile: 6.0.3 - mdast-util-to-markdown@1.5.0: - dependencies: - '@types/mdast': 3.0.15 - '@types/unist': 2.0.11 - longest-streak: 3.1.0 - mdast-util-phrasing: 3.0.1 - mdast-util-to-string: 3.2.0 - micromark-util-decode-string: 1.1.0 - unist-util-visit: 4.1.2 - zwitch: 2.0.4 - mdast-util-to-markdown@2.1.2: dependencies: '@types/mdast': 4.0.4 @@ -20282,10 +23479,6 @@ snapshots: unist-util-visit: 5.0.0 zwitch: 2.0.4 - mdast-util-to-string@3.2.0: - dependencies: - '@types/mdast': 3.0.15 - mdast-util-to-string@4.0.0: dependencies: '@types/mdast': 4.0.4 @@ -20307,8 +23500,6 @@ snapshots: tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 - meow@12.1.1: {} - meow@13.2.0: {} merge-descriptors@1.0.3: {} @@ -20321,24 +23512,11 @@ snapshots: methods@1.1.2: {} - micromark-core-commonmark@1.1.0: + micro@9.3.5-canary.3: dependencies: - decode-named-character-reference: 1.2.0 - micromark-factory-destination: 1.1.0 - micromark-factory-label: 1.1.0 - micromark-factory-space: 1.1.0 - micromark-factory-title: 1.1.0 - micromark-factory-whitespace: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-chunked: 1.1.0 - micromark-util-classify-character: 1.1.0 - micromark-util-html-tag-name: 1.2.0 - micromark-util-normalize-identifier: 1.1.0 - micromark-util-resolve-all: 1.1.0 - micromark-util-subtokenize: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 + arg: 4.1.0 + content-type: 1.0.4 + raw-body: 2.4.1 micromark-core-commonmark@2.0.3: dependencies: @@ -20376,13 +23554,6 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-extension-gfm-autolink-literal@1.0.5: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-sanitize-uri: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - micromark-extension-gfm-autolink-literal@2.1.0: dependencies: micromark-util-character: 2.1.1 @@ -20390,17 +23561,6 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-extension-gfm-footnote@1.1.2: - dependencies: - micromark-core-commonmark: 1.1.0 - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-normalize-identifier: 1.1.0 - micromark-util-sanitize-uri: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - micromark-extension-gfm-footnote@2.1.0: dependencies: devlop: 1.1.0 @@ -20412,15 +23572,6 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-extension-gfm-strikethrough@1.0.7: - dependencies: - micromark-util-chunked: 1.1.0 - micromark-util-classify-character: 1.1.0 - micromark-util-resolve-all: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - micromark-extension-gfm-strikethrough@2.1.0: dependencies: devlop: 1.1.0 @@ -20430,14 +23581,6 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-extension-gfm-table@1.0.7: - dependencies: - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - micromark-extension-gfm-table@2.1.1: dependencies: devlop: 1.1.0 @@ -20446,22 +23589,10 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-extension-gfm-tagfilter@1.0.2: - dependencies: - micromark-util-types: 1.1.0 - micromark-extension-gfm-tagfilter@2.0.0: dependencies: micromark-util-types: 2.0.2 - micromark-extension-gfm-task-list-item@1.0.5: - dependencies: - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - micromark-extension-gfm-task-list-item@2.1.0: dependencies: devlop: 1.1.0 @@ -20470,17 +23601,6 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-extension-gfm@2.0.3: - dependencies: - micromark-extension-gfm-autolink-literal: 1.0.5 - micromark-extension-gfm-footnote: 1.1.2 - micromark-extension-gfm-strikethrough: 1.0.7 - micromark-extension-gfm-table: 1.0.7 - micromark-extension-gfm-tagfilter: 1.0.2 - micromark-extension-gfm-task-list-item: 1.0.5 - micromark-util-combine-extensions: 1.1.0 - micromark-util-types: 1.1.0 - micromark-extension-gfm@3.0.0: dependencies: micromark-extension-gfm-autolink-literal: 2.1.0 @@ -20543,25 +23663,12 @@ snapshots: micromark-util-combine-extensions: 2.0.1 micromark-util-types: 2.0.2 - micromark-factory-destination@1.1.0: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - micromark-factory-destination@2.0.1: dependencies: micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-factory-label@1.1.0: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - micromark-factory-label@2.0.1: dependencies: devlop: 1.1.0 @@ -20591,13 +23698,6 @@ snapshots: micromark-util-character: 2.1.1 micromark-util-types: 2.0.2 - micromark-factory-title@1.1.0: - dependencies: - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - micromark-factory-title@2.0.1: dependencies: micromark-factory-space: 2.0.1 @@ -20605,13 +23705,6 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-factory-whitespace@1.1.0: - dependencies: - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - micromark-factory-whitespace@2.0.1: dependencies: micromark-factory-space: 2.0.1 @@ -20629,51 +23722,25 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-util-chunked@1.1.0: - dependencies: - micromark-util-symbol: 1.1.0 - micromark-util-chunked@2.0.1: dependencies: micromark-util-symbol: 2.0.1 - micromark-util-classify-character@1.1.0: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - micromark-util-classify-character@2.0.1: dependencies: micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-util-combine-extensions@1.1.0: - dependencies: - micromark-util-chunked: 1.1.0 - micromark-util-types: 1.1.0 - micromark-util-combine-extensions@2.0.1: dependencies: micromark-util-chunked: 2.0.1 micromark-util-types: 2.0.2 - micromark-util-decode-numeric-character-reference@1.1.0: - dependencies: - micromark-util-symbol: 1.1.0 - micromark-util-decode-numeric-character-reference@2.0.2: dependencies: micromark-util-symbol: 2.0.1 - micromark-util-decode-string@1.1.0: - dependencies: - decode-named-character-reference: 1.2.0 - micromark-util-character: 1.2.0 - micromark-util-decode-numeric-character-reference: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-decode-string@2.0.1: dependencies: decode-named-character-reference: 1.2.0 @@ -20681,8 +23748,6 @@ snapshots: micromark-util-decode-numeric-character-reference: 2.0.2 micromark-util-symbol: 2.0.1 - micromark-util-encode@1.1.0: {} - micromark-util-encode@2.0.1: {} micromark-util-events-to-acorn@2.0.3: @@ -20695,45 +23760,22 @@ snapshots: micromark-util-types: 2.0.2 vfile-message: 4.0.3 - micromark-util-html-tag-name@1.2.0: {} - micromark-util-html-tag-name@2.0.1: {} - micromark-util-normalize-identifier@1.1.0: - dependencies: - micromark-util-symbol: 1.1.0 - micromark-util-normalize-identifier@2.0.1: dependencies: micromark-util-symbol: 2.0.1 - micromark-util-resolve-all@1.1.0: - dependencies: - micromark-util-types: 1.1.0 - micromark-util-resolve-all@2.0.1: dependencies: micromark-util-types: 2.0.2 - micromark-util-sanitize-uri@1.2.0: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-encode: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-sanitize-uri@2.0.1: dependencies: micromark-util-character: 2.1.1 micromark-util-encode: 2.0.1 micromark-util-symbol: 2.0.1 - micromark-util-subtokenize@1.1.0: - dependencies: - micromark-util-chunked: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - micromark-util-subtokenize@2.1.0: dependencies: devlop: 1.1.0 @@ -20749,28 +23791,6 @@ snapshots: micromark-util-types@2.0.2: {} - micromark@3.2.0: - dependencies: - '@types/debug': 4.1.12 - debug: 4.4.3 - decode-named-character-reference: 1.2.0 - micromark-core-commonmark: 1.1.0 - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-chunked: 1.1.0 - micromark-util-combine-extensions: 1.1.0 - micromark-util-decode-numeric-character-reference: 1.1.0 - micromark-util-encode: 1.1.0 - micromark-util-normalize-identifier: 1.1.0 - micromark-util-resolve-all: 1.1.0 - micromark-util-sanitize-uri: 1.2.0 - micromark-util-subtokenize: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - transitivePeerDependencies: - - supports-color - micromark@4.0.2: dependencies: '@types/debug': 4.1.12 @@ -20804,7 +23824,7 @@ snapshots: mime-db@1.54.0: {} - mime-format@2.0.1: + mime-format@2.0.2: dependencies: charset: 1.0.1 @@ -20836,11 +23856,11 @@ snapshots: min-indent@1.0.1: {} - mini-css-extract-plugin@2.9.4(webpack@5.104.1): + mini-css-extract-plugin@2.9.4(webpack@5.104.1(@swc/core@1.15.24)): dependencies: schema-utils: 4.3.3 tapable: 2.3.0 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) minimalistic-assert@1.0.1: {} @@ -20848,10 +23868,18 @@ snapshots: dependencies: '@isaacs/brace-expansion': 5.0.0 + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.12 + minimatch@5.1.6: dependencies: brace-expansion: 2.0.2 @@ -20886,12 +23914,21 @@ snapshots: dependencies: minipass: 3.3.6 + minipass@2.9.0: + dependencies: + safe-buffer: 5.2.1 + yallist: 3.1.1 + minipass@3.3.6: dependencies: yallist: 4.0.0 minipass@7.1.2: {} + minizlib@1.3.3: + dependencies: + minipass: 2.9.0 + minizlib@3.1.0: dependencies: minipass: 7.1.2 @@ -20904,6 +23941,8 @@ snapshots: mkdirp@1.0.4: {} + mkdirp@3.0.1: {} + mlly@1.8.0: dependencies: acorn: 8.15.0 @@ -20923,6 +23962,10 @@ snapshots: ms@2.0.0: {} + ms@2.1.1: {} + + ms@2.1.2: {} + ms@2.1.3: {} multer@2.0.2: @@ -20942,7 +23985,7 @@ snapshots: mustache@4.2.0: {} - mute-stream@2.0.0: {} + mute-stream@3.0.0: {} mz@2.7.0: dependencies: @@ -20953,10 +23996,10 @@ snapshots: nan@2.24.0: optional: true - nano-spawn@2.0.0: {} - nanoid@3.3.11: {} + nanoid@5.1.7: {} + napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} @@ -21004,6 +24047,18 @@ snapshots: dependencies: http2-client: 1.3.5 + node-fetch@2.6.7(encoding@0.1.13): + dependencies: + whatwg-url: 5.0.0 + optionalDependencies: + encoding: 0.1.13 + + node-fetch@2.6.9(encoding@0.1.13): + dependencies: + whatwg-url: 5.0.0 + optionalDependencies: + encoding: 0.1.13 + node-fetch@2.7.0(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 @@ -21012,6 +24067,8 @@ snapshots: node-forge@1.3.3: {} + node-gyp-build@4.8.4: {} + node-gyp@12.1.0: dependencies: env-paths: 2.2.1 @@ -21020,7 +24077,7 @@ snapshots: make-fetch-happen: 15.0.3 nopt: 9.0.0 proc-log: 6.1.0 - semver: 7.7.3 + semver: 7.7.4 tar: 7.5.2 tinyglobby: 0.2.15 which: 6.0.0 @@ -21040,20 +24097,18 @@ snapshots: '@types/sarif': 2.1.7 fs-extra: 10.1.0 - nopt@9.0.0: + nopt@8.1.0: dependencies: - abbrev: 4.0.0 + abbrev: 3.0.1 - normalize-package-data@6.0.2: + nopt@9.0.0: dependencies: - hosted-git-info: 7.0.2 - semver: 7.7.3 - validate-npm-package-license: 3.0.4 + abbrev: 4.0.0 normalize-package-data@7.0.1: dependencies: hosted-git-info: 8.1.0 - semver: 7.7.3 + semver: 7.7.4 validate-npm-package-license: 3.0.4 normalize-path@3.0.0: {} @@ -21062,22 +24117,15 @@ snapshots: npm-install-checks@8.0.0: dependencies: - semver: 7.7.3 + semver: 7.7.4 npm-normalize-package-bin@5.0.0: {} - npm-package-arg@12.0.2: - dependencies: - hosted-git-info: 8.1.0 - proc-log: 5.0.0 - semver: 7.7.3 - validate-npm-package-name: 6.0.2 - npm-package-arg@13.0.2: dependencies: hosted-git-info: 9.0.2 proc-log: 6.1.0 - semver: 7.7.3 + semver: 7.7.4 validate-npm-package-name: 7.0.0 npm-pick-manifest@11.0.3: @@ -21085,7 +24133,7 @@ snapshots: npm-install-checks: 8.0.0 npm-normalize-package-bin: 5.0.0 npm-package-arg: 13.0.2 - semver: 7.7.3 + semver: 7.7.4 npm-run-path@4.0.1: dependencies: @@ -21096,17 +24144,24 @@ snapshots: path-key: 4.0.0 unicorn-magic: 0.3.0 + npmlog@7.0.1: + dependencies: + are-we-there-yet: 4.0.2 + console-control-strings: 1.1.0 + gauge: 5.0.2 + set-blocking: 2.0.0 + nprogress@0.2.0: {} nth-check@2.1.1: dependencies: boolbase: 1.0.0 - null-loader@4.0.1(webpack@5.104.1): + null-loader@4.0.1(webpack@5.104.1(@swc/core@1.15.24)): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) nwsapi@2.2.23: {} @@ -21156,6 +24211,11 @@ snapshots: object-inspect@1.13.4: {} + object-is@1.1.6: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + object-keys@1.1.1: {} object.assign@4.1.7: @@ -21206,6 +24266,10 @@ snapshots: on-headers@1.1.0: {} + once@1.3.3: + dependencies: + wrappy: 1.0.2 + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -21231,12 +24295,12 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 - openapi-to-postmanv2@4.25.0(encoding@0.1.13): + openapi-to-postmanv2@6.0.0(encoding@0.1.13): dependencies: - ajv: 8.11.0 - ajv-draft-04: 1.0.0(ajv@8.11.0) - ajv-formats: 2.1.1(ajv@8.11.0) - async: 3.2.4 + ajv: 8.17.1 + ajv-draft-04: 1.0.0(ajv@8.17.1) + ajv-formats: 2.1.1(ajv@8.17.1) + async: 3.2.6 commander: 2.20.3 graphlib: 2.1.8 js-yaml: 4.1.0 @@ -21246,13 +24310,16 @@ snapshots: neotraverse: 0.6.15 oas-resolver-browser: 2.5.6 object-hash: 3.0.0 + openapi-types: 12.1.3 path-browserify: 1.0.1 - postman-collection: 4.5.0 + postman-collection: 5.3.0 swagger2openapi: 7.0.8(encoding@0.1.13) yaml: 1.10.2 transitivePeerDependencies: - encoding + openapi-types@12.1.3: {} + opener@1.5.2: {} optionator@0.9.4: @@ -21264,17 +24331,7 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - ora@8.2.0: - dependencies: - chalk: 5.6.2 - cli-cursor: 5.0.0 - cli-spinners: 2.9.2 - is-interactive: 2.0.0 - is-unicode-supported: 2.1.0 - log-symbols: 6.0.0 - stdin-discarder: 0.2.2 - string-width: 7.2.0 - strip-ansi: 7.1.2 + os-paths@4.4.0: {} own-keys@1.0.1: dependencies: @@ -21286,6 +24343,8 @@ snapshots: p-finally@1.0.0: {} + p-finally@2.0.1: {} + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -21298,7 +24357,7 @@ snapshots: dependencies: yocto-queue: 1.2.2 - p-limit@7.2.0: + p-limit@7.3.0: dependencies: yocto-queue: 1.2.2 @@ -21320,20 +24379,16 @@ snapshots: p-map@7.0.4: {} - p-pipe@4.0.0: {} - p-queue@6.6.2: dependencies: eventemitter3: 4.0.7 p-timeout: 3.2.0 - p-queue@9.0.1: + p-queue@9.1.2: dependencies: eventemitter3: 5.0.1 p-timeout: 7.0.1 - p-reduce@3.0.0: {} - p-retry@6.2.1: dependencies: '@types/retry': 0.12.2 @@ -21355,7 +24410,7 @@ snapshots: got: 12.6.1 registry-auth-token: 5.1.0 registry-url: 6.0.1 - semver: 7.7.3 + semver: 7.7.4 pako@2.1.0: {} @@ -21389,16 +24444,14 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - parse-json@8.3.0: - dependencies: - '@babel/code-frame': 7.27.1 - index-to-position: 1.2.0 - type-fest: 4.41.0 + parse-ms@2.1.0: {} parse-ms@4.0.0: {} parse-numeric-range@1.3.0: {} + parse-passwd@1.0.0: {} + parse-path@7.1.0: dependencies: protocols: 2.0.2 @@ -21413,8 +24466,6 @@ snapshots: domhandler: 5.0.3 parse5: 7.3.0 - parse5@6.0.1: {} - parse5@7.3.0: dependencies: entities: 6.0.1 @@ -21432,6 +24483,8 @@ snapshots: path-exists@5.0.0: {} + path-expression-matcher@1.5.0: {} + path-is-absolute@1.0.1: {} path-is-inside@1.0.2: {} @@ -21440,6 +24493,11 @@ snapshots: path-key@4.0.0: {} + path-match@1.2.4: + dependencies: + http-errors: 1.4.0 + path-to-regexp: 1.9.0 + path-parse@1.0.7: {} path-scurry@1.11.1: @@ -21460,31 +24518,32 @@ snapshots: path-to-regexp@3.3.0: {} - path-to-regexp@8.3.0: {} + path-to-regexp@6.1.0: {} - path-type@4.0.0: {} + path-to-regexp@6.2.1: {} - path-type@6.0.0: {} + path-to-regexp@6.3.0: {} - path@0.12.7: - dependencies: - process: 0.11.10 - util: 0.10.4 + path-to-regexp@8.3.0: {} + + path-type@4.0.0: {} pathe@2.0.3: {} - pg-cloudflare@1.2.7: + pend@1.2.0: {} + + pg-cloudflare@1.3.0: optional: true - pg-connection-string@2.9.1: {} + pg-connection-string@2.12.0: {} pg-int8@1.0.1: {} - pg-pool@3.10.1(pg@8.16.3): + pg-pool@3.13.0(pg@8.20.0): dependencies: - pg: 8.16.3 + pg: 8.20.0 - pg-protocol@1.10.3: {} + pg-protocol@1.13.0: {} pg-types@2.2.0: dependencies: @@ -21494,15 +24553,15 @@ snapshots: postgres-date: 1.0.7 postgres-interval: 1.2.0 - pg@8.16.3: + pg@8.20.0: dependencies: - pg-connection-string: 2.9.1 - pg-pool: 3.10.1(pg@8.16.3) - pg-protocol: 1.10.3 + pg-connection-string: 2.12.0 + pg-pool: 3.13.0(pg@8.20.0) + pg-protocol: 1.13.0 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: - pg-cloudflare: 1.2.7 + pg-cloudflare: 1.3.0 pgpass@1.0.5: dependencies: @@ -21510,15 +24569,15 @@ snapshots: pgvector@0.2.1: {} + picocolors@1.0.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} picomatch@4.0.3: {} - pidtree@0.6.0: {} - - pify@6.1.0: {} + picomatch@4.0.4: {} pirates@4.0.7: {} @@ -21686,22 +24745,22 @@ snapshots: '@csstools/utilities': 2.0.0(postcss@8.5.6) postcss: 8.5.6 - postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(yaml@2.8.2): + postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(yaml@2.8.3): dependencies: lilconfig: 3.1.3 optionalDependencies: jiti: 2.6.1 postcss: 8.5.6 tsx: 4.21.0 - yaml: 2.8.2 + yaml: 2.8.3 - postcss-loader@7.3.4(postcss@8.5.6)(typescript@5.9.3)(webpack@5.104.1): + postcss-loader@7.3.4(postcss@8.5.6)(typescript@6.0.2)(webpack@5.104.1(@swc/core@1.15.24)): dependencies: - cosmiconfig: 8.3.6(typescript@5.9.3) + cosmiconfig: 8.3.6(typescript@6.0.2) jiti: 1.21.7 postcss: 8.5.6 - semver: 7.7.3 - webpack: 5.104.1 + semver: 7.7.4 + webpack: 5.104.1(@swc/core@1.15.24) transitivePeerDependencies: - typescript @@ -22004,36 +25063,35 @@ snapshots: dependencies: xtend: 4.0.2 - postman-code-generators@1.14.2: + postman-code-generators@2.1.1: dependencies: - async: 3.2.2 + async: 3.2.6 detect-package-manager: 3.0.2 lodash: 4.17.21 - path: 0.12.7 - postman-collection: 4.5.0 + postman-collection: 5.3.0 shelljs: 0.8.5 - postman-collection@4.5.0: + postman-collection@5.3.0: dependencies: '@faker-js/faker': 5.5.3 file-type: 3.9.0 http-reasons: 0.1.0 iconv-lite: 0.6.3 liquid-json: 0.3.1 - lodash: 4.17.21 - mime-format: 2.0.1 - mime-types: 2.1.35 - postman-url-encoder: 3.0.5 - semver: 7.6.3 + lodash: 4.17.23 + mime: 3.0.0 + mime-format: 2.0.2 + postman-url-encoder: 3.0.8 + semver: 7.7.1 uuid: 8.3.2 - postman-url-encoder@3.0.5: + postman-url-encoder@3.0.8: dependencies: punycode: 2.3.1 prelude-ls@1.2.1: {} - prettier-linter-helpers@1.0.0: + prettier-linter-helpers@1.0.1: dependencies: fast-diff: 1.3.0 @@ -22049,11 +25107,11 @@ snapshots: sort-object-keys: 1.1.3 sort-order: 1.1.2 - prettier@3.7.4: {} + prettier@3.8.1: {} pretty-error@4.0.0: dependencies: - lodash: 4.17.21 + lodash: 4.17.23 renderkid: 3.0.0 pretty-format@27.5.1: @@ -22068,6 +25126,16 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 + pretty-format@30.3.0: + dependencies: + '@jest/schemas': 30.0.5 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + pretty-ms@7.0.1: + dependencies: + parse-ms: 2.1.0 + pretty-ms@9.3.0: dependencies: parse-ms: 4.0.0 @@ -22076,16 +25144,14 @@ snapshots: printable-characters@1.0.42: {} - prism-react-renderer@2.4.1(react@19.2.3): + prism-react-renderer@2.4.1(react@19.2.5): dependencies: '@types/prismjs': 1.26.5 clsx: 2.1.1 - react: 19.2.3 + react: 19.2.5 prismjs@1.30.0: {} - proc-log@5.0.0: {} - proc-log@6.1.0: {} process-nextick-args@2.0.1: {} @@ -22101,6 +25167,8 @@ snapshots: dependencies: asap: 2.0.6 + promisepipe@3.0.0: {} + prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -22118,11 +25186,12 @@ snapshots: retry: 0.12.0 signal-exit: 3.0.7 - properties-reader@2.3.0: + properties-reader@3.0.1: dependencies: - mkdirp: 1.0.4 - - property-information@6.5.0: {} + '@kwsites/file-exists': 1.1.1 + mkdirp: 3.0.1 + transitivePeerDependencies: + - supports-color property-information@7.1.0: {} @@ -22140,7 +25209,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 25.0.3 + '@types/node': 25.5.2 long: 5.3.2 protocols@2.0.2: {} @@ -22150,6 +25219,8 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + proxy-from-env@2.1.0: {} + pump@3.0.3: dependencies: end-of-stream: 1.4.5 @@ -22165,14 +25236,8 @@ snapshots: dependencies: escape-goat: 4.0.0 - pure-rand@6.1.0: {} - pure-rand@7.0.1: {} - qs@6.14.0: - dependencies: - side-channel: 1.1.0 - qs@6.14.1: dependencies: side-channel: 1.1.0 @@ -22191,6 +25256,13 @@ snapshots: range-parser@1.2.1: {} + raw-body@2.4.1: + dependencies: + bytes: 3.1.0 + http-errors: 1.7.3 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + raw-body@2.5.3: dependencies: bytes: 3.1.2 @@ -22212,16 +25284,16 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-dom@19.2.3(react@19.2.3): + react-dom@19.2.5(react@19.2.5): dependencies: - react: 19.2.3 + react: 19.2.5 scheduler: 0.27.0 react-fast-compare@3.2.2: {} - react-hook-form@7.70.0(react@19.2.3): + react-hook-form@7.70.0(react@19.2.5): dependencies: - react: 19.2.3 + react: 19.2.5 react-is@16.13.1: {} @@ -22229,89 +25301,82 @@ snapshots: react-is@18.3.1: {} - react-json-view-lite@2.5.0(react@19.2.3): + react-json-view-lite@2.5.0(react@19.2.5): dependencies: - react: 19.2.3 + react: 19.2.5 react-lifecycles-compat@3.0.4: {} - react-live@4.1.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + react-live@4.1.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: - prism-react-renderer: 2.4.1(react@19.2.3) - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + prism-react-renderer: 2.4.1(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) sucrase: 3.35.1 - use-editable: 2.3.3(react@19.2.3) + use-editable: 2.3.3(react@19.2.5) - react-loadable-ssr-addon-v5-slorber@1.0.1(@docusaurus/react-loadable@6.0.0(react@19.2.3))(webpack@5.104.1): + react-loadable-ssr-addon-v5-slorber@1.0.3(@docusaurus/react-loadable@6.0.0(react@19.2.5))(webpack@5.104.1(@swc/core@1.15.24)): dependencies: '@babel/runtime': 7.28.4 - react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.3)' - webpack: 5.104.1 + react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.2.5)' + webpack: 5.104.1(@swc/core@1.15.24) react-magic-dropzone@1.0.1: {} - react-markdown@8.0.7(@types/react@19.2.7)(react@19.2.3): + react-markdown@10.1.0(@types/react@19.2.7)(react@19.2.5): dependencies: - '@types/hast': 2.3.10 - '@types/prop-types': 15.7.15 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 '@types/react': 19.2.7 - '@types/unist': 2.0.11 - comma-separated-tokens: 2.0.3 - hast-util-whitespace: 2.0.1 - prop-types: 15.8.1 - property-information: 6.5.0 - react: 19.2.3 - react-is: 18.3.1 - remark-parse: 10.0.2 - remark-rehype: 10.1.0 - space-separated-tokens: 2.0.2 - style-to-object: 0.4.4 - unified: 10.1.2 - unist-util-visit: 4.1.2 - vfile: 5.3.7 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.1 + react: 19.2.5 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.0.0 + vfile: 6.0.3 transitivePeerDependencies: - supports-color - react-modal@3.16.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + react-modal@3.16.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: exenv: 1.2.2 prop-types: 15.8.1 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) react-lifecycles-compat: 3.0.4 warning: 4.0.3 - react-redux@7.2.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + react-redux@9.2.0(@types/react@19.2.7)(react@19.2.5)(redux@5.0.1): dependencies: - '@babel/runtime': 7.28.4 - '@types/react-redux': 7.1.34 - hoist-non-react-statics: 3.3.2 - loose-envify: 1.4.0 - prop-types: 15.8.1 - react: 19.2.3 - react-is: 17.0.2 + '@types/use-sync-external-store': 0.0.6 + react: 19.2.5 + use-sync-external-store: 1.6.0(react@19.2.5) optionalDependencies: - react-dom: 19.2.3(react@19.2.3) + '@types/react': 19.2.7 + redux: 5.0.1 - react-router-config@5.1.1(react-router@5.3.4(react@19.2.3))(react@19.2.3): + react-router-config@5.1.1(react-router@5.3.4(react@19.2.5))(react@19.2.5): dependencies: '@babel/runtime': 7.28.4 - react: 19.2.3 - react-router: 5.3.4(react@19.2.3) + react: 19.2.5 + react-router: 5.3.4(react@19.2.5) - react-router-dom@5.3.4(react@19.2.3): + react-router-dom@5.3.4(react@19.2.5): dependencies: '@babel/runtime': 7.28.4 history: 4.10.1 loose-envify: 1.4.0 prop-types: 15.8.1 - react: 19.2.3 - react-router: 5.3.4(react@19.2.3) + react: 19.2.5 + react-router: 5.3.4(react@19.2.5) tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - react-router@5.3.4(react@19.2.3): + react-router@5.3.4(react@19.2.5): dependencies: '@babel/runtime': 7.28.4 history: 4.10.1 @@ -22319,25 +25384,12 @@ snapshots: loose-envify: 1.4.0 path-to-regexp: 1.9.0 prop-types: 15.8.1 - react: 19.2.3 + react: 19.2.5 react-is: 16.13.1 tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - react@19.2.3: {} - - read-pkg@9.0.1: - dependencies: - '@types/normalize-package-data': 2.4.4 - normalize-package-data: 6.0.2 - parse-json: 8.3.0 - type-fest: 4.41.0 - unicorn-magic: 0.1.0 - - read-yaml-file@2.1.0: - dependencies: - js-yaml: 4.1.1 - strip-bom: 4.0.0 + react@19.2.5: {} readable-stream@2.3.8: dependencies: @@ -22352,7 +25404,7 @@ snapshots: readable-stream@3.6.2: dependencies: inherits: 2.0.4 - string_decoder: 1.1.1 + string_decoder: 1.3.0 util-deprecate: 1.0.2 readable-stream@4.7.0: @@ -22411,13 +25463,11 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 - redux-thunk@2.4.2(redux@4.2.1): + redux-thunk@3.1.0(redux@5.0.1): dependencies: - redux: 4.2.1 + redux: 5.0.1 - redux@4.2.1: - dependencies: - '@babel/runtime': 7.28.4 + redux@5.0.1: {} reflect-metadata@0.2.2: {} @@ -22474,12 +25524,6 @@ snapshots: dependencies: jsesc: 3.1.0 - rehype-raw@6.1.1: - dependencies: - '@types/hast': 2.3.10 - hast-util-raw: 7.2.3 - unified: 10.1.2 - rehype-raw@7.0.0: dependencies: '@types/hast': 3.0.4 @@ -22539,15 +25583,6 @@ snapshots: transitivePeerDependencies: - supports-color - remark-gfm@3.0.1: - dependencies: - '@types/mdast': 3.0.15 - mdast-util-gfm: 2.0.2 - micromark-extension-gfm: 2.0.3 - unified: 10.1.2 - transitivePeerDependencies: - - supports-color - remark-gfm@4.0.1: dependencies: '@types/mdast': 4.0.4 @@ -22566,14 +25601,6 @@ snapshots: transitivePeerDependencies: - supports-color - remark-parse@10.0.2: - dependencies: - '@types/mdast': 3.0.15 - mdast-util-from-markdown: 1.3.1 - unified: 10.1.2 - transitivePeerDependencies: - - supports-color - remark-parse@11.0.0: dependencies: '@types/mdast': 4.0.4 @@ -22583,13 +25610,6 @@ snapshots: transitivePeerDependencies: - supports-color - remark-rehype@10.1.0: - dependencies: - '@types/hast': 2.3.10 - '@types/mdast': 3.0.15 - mdast-util-to-hast: 12.3.0 - unified: 10.1.2 - remark-rehype@11.1.2: dependencies: '@types/hast': 3.0.4 @@ -22609,7 +25629,7 @@ snapshots: css-select: 4.3.0 dom-converter: 0.2.0 htmlparser2: 6.1.0 - lodash: 4.17.21 + lodash: 4.17.23 strip-ansi: 6.0.1 repeat-string@1.6.1: {} @@ -22624,7 +25644,7 @@ snapshots: requires-port@1.0.0: {} - reselect@4.1.8: {} + reselect@5.1.1: {} reserved@0.1.2: {} @@ -22636,10 +25656,20 @@ snapshots: dependencies: resolve-from: 5.0.0 + resolve-dir@1.0.1: + dependencies: + expand-tilde: 2.0.2 + global-modules: 1.0.0 + resolve-from@4.0.0: {} resolve-from@5.0.0: {} + resolve-path@1.4.0: + dependencies: + http-errors: 1.6.3 + path-is-absolute: 1.0.1 + resolve-pathname@3.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -22667,15 +25697,6 @@ snapshots: retry-as-promised@7.1.1: {} - retry-request@7.0.2(encoding@0.1.13): - dependencies: - '@types/request': 2.48.13 - extend: 3.0.2 - teeny-request: 9.0.0(encoding@0.1.13) - transitivePeerDependencies: - - encoding - - supports-color - retry@0.12.0: {} retry@0.13.1: {} @@ -22741,10 +25762,6 @@ snapshots: dependencies: queue-microtask: 1.2.3 - sade@1.8.1: - dependencies: - mri: 1.2.0 - safe-array-concat@1.1.3: dependencies: call-bind: 1.0.8 @@ -22772,12 +25789,13 @@ snapshots: safer-buffer@2.1.2: {} - sass-loader@16.0.6(sass@1.97.2)(webpack@5.104.1): + sass-loader@16.0.6(@rspack/core@1.7.11)(sass@1.97.2)(webpack@5.104.1(@swc/core@1.15.24)): dependencies: neo-async: 2.6.2 optionalDependencies: + '@rspack/core': 1.7.11 sass: 1.97.2 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) sass@1.97.2: dependencies: @@ -22802,8 +25820,8 @@ snapshots: schema-utils@3.3.0: dependencies: '@types/json-schema': 7.0.15 - ajv: 6.12.6 - ajv-keywords: 3.5.2(ajv@6.12.6) + ajv: 6.14.0 + ajv-keywords: 3.5.2(ajv@6.14.0) schema-utils@4.3.3: dependencies: @@ -22828,14 +25846,20 @@ snapshots: semver-diff@4.0.0: dependencies: - semver: 7.7.3 + semver: 7.7.4 semver@6.3.1: {} - semver@7.6.3: {} + semver@7.5.4: + dependencies: + lru-cache: 6.0.0 + + semver@7.7.1: {} semver@7.7.3: {} + semver@7.7.4: {} + send@0.19.2: dependencies: debug: 2.6.9 @@ -22877,15 +25901,15 @@ snapshots: sequelize-pool@7.1.0: {} - sequelize-typescript@2.1.6(@types/node@25.0.3)(@types/validator@13.15.10)(reflect-metadata@0.2.2)(sequelize@6.37.7(pg@8.16.3)): + sequelize-typescript@2.1.6(@types/node@25.5.2)(@types/validator@13.15.10)(reflect-metadata@0.2.2)(sequelize@6.37.7(pg@8.20.0)): dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 '@types/validator': 13.15.10 glob: 7.2.0 reflect-metadata: 0.2.2 - sequelize: 6.37.7(pg@8.16.3) + sequelize: 6.37.7(pg@8.20.0) - sequelize@6.37.7(pg@8.16.3): + sequelize@6.37.7(pg@8.20.0): dependencies: '@types/debug': 4.1.12 '@types/validator': 13.15.10 @@ -22895,16 +25919,16 @@ snapshots: lodash: 4.17.21 moment: 2.30.1 moment-timezone: 0.5.48 - pg-connection-string: 2.9.1 + pg-connection-string: 2.12.0 retry-as-promised: 7.1.1 - semver: 7.7.3 + semver: 7.7.4 sequelize-pool: 7.1.0 toposort-class: 1.0.1 uuid: 8.3.2 validator: 13.15.26 wkx: 0.5.0 optionalDependencies: - pg: 8.16.3 + pg: 8.20.0 transitivePeerDependencies: - supports-color @@ -22912,12 +25936,12 @@ snapshots: dependencies: randombytes: 2.1.0 - serve-handler@6.1.6: + serve-handler@6.1.7: dependencies: bytes: 3.0.0 content-disposition: 0.5.2 mime-types: 2.1.18 - minimatch: 3.1.2 + minimatch: 3.1.5 path-is-inside: 1.0.2 path-to-regexp: 3.3.0 range-parser: 1.2.0 @@ -22980,6 +26004,8 @@ snapshots: setprototypeof@1.1.0: {} + setprototypeof@1.1.1: {} + setprototypeof@1.2.0: {} shallow-clone@3.0.1: @@ -23058,12 +26084,24 @@ snapshots: signal-exit@3.0.7: {} + signal-exit@4.0.2: {} + signal-exit@4.1.0: {} simple-eval@1.0.1: dependencies: jsep: 1.4.0 + simple-git@3.36.0: + dependencies: + '@kwsites/file-exists': 1.1.1 + '@kwsites/promise-deferred': 1.1.1 + '@simple-git/args-pathspec': 1.0.3 + '@simple-git/argv-parser': 1.1.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + sirv@2.0.4: dependencies: '@polka/url': 1.0.0-next.29 @@ -23087,8 +26125,6 @@ snapshots: slash@4.0.0: {} - slash@5.1.0: {} - slice-ansi@7.1.2: dependencies: ansi-styles: 6.2.3 @@ -23124,10 +26160,6 @@ snapshots: sort-css-media-queries@2.2.0: {} - sort-keys@5.1.0: - dependencies: - is-plain-obj: 4.1.0 - sort-keys@6.0.0: dependencies: is-plain-obj: 4.1.0 @@ -23229,24 +26261,33 @@ snapshots: as-table: 1.0.55 get-source: 2.0.12 + stat-mode@0.3.0: {} + statuses@1.5.0: {} statuses@2.0.2: {} std-env@3.10.0: {} - stdin-discarder@0.2.2: {} - stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 internal-slot: 1.1.0 - stream-events@1.0.5: + stream-browserify@3.0.0: + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + + stream-to-array@2.3.0: dependencies: - stubs: 3.0.0 + any-promise: 1.3.0 - stream-shift@1.0.3: {} + stream-to-promise@2.2.0: + dependencies: + any-promise: 1.3.0 + end-of-stream: 1.1.0 + stream-to-array: 2.3.0 streamsearch@1.1.0: {} @@ -23386,18 +26427,12 @@ snapshots: strip-json-comments@3.1.1: {} - strnum@1.1.2: {} - - stubs@3.0.0: {} + strnum@2.2.3: {} style-to-js@1.1.21: dependencies: style-to-object: 1.0.14 - style-to-object@0.4.4: - dependencies: - inline-style-parser: 0.1.1 - style-to-object@1.0.14: dependencies: inline-style-parser: 0.2.7 @@ -23480,11 +26515,17 @@ snapshots: transitivePeerDependencies: - encoding - swr@2.3.8(react@19.2.3): + swc-loader@0.2.7(@swc/core@1.15.24)(webpack@5.104.1(@swc/core@1.15.24)): + dependencies: + '@swc/core': 1.15.24 + '@swc/counter': 0.1.3 + webpack: 5.104.1(@swc/core@1.15.24) + + swr@2.3.8(react@19.2.5): dependencies: dequal: 2.0.3 - react: 19.2.3 - use-sync-external-store: 1.6.0(react@19.2.3) + react: 19.2.5 + use-sync-external-store: 1.6.0(react@19.2.5) symbol-tree@3.2.4: {} @@ -23492,27 +26533,44 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 - syncpack@13.0.4(typescript@5.9.3): + synckit@0.11.12: dependencies: - chalk: 5.6.2 - chalk-template: 1.1.2 - commander: 13.1.0 - cosmiconfig: 9.0.0(typescript@5.9.3) - effect: 3.19.12 - enquirer: 2.4.1 - fast-check: 3.23.2 - globby: 14.1.0 - jsonc-parser: 3.3.1 - minimatch: 9.0.5 - npm-package-arg: 12.0.2 - ora: 8.2.0 - prompts: 2.4.2 - read-yaml-file: 2.1.0 - semver: 7.7.3 - tightrope: 0.2.0 - ts-toolbelt: 9.6.0 - transitivePeerDependencies: - - typescript + '@pkgr/core': 0.2.9 + + syncpack-darwin-arm64@14.3.0: + optional: true + + syncpack-darwin-x64@14.3.0: + optional: true + + syncpack-linux-arm64-musl@14.3.0: + optional: true + + syncpack-linux-arm64@14.3.0: + optional: true + + syncpack-linux-x64-musl@14.3.0: + optional: true + + syncpack-linux-x64@14.3.0: + optional: true + + syncpack-windows-arm64@14.3.0: + optional: true + + syncpack-windows-x64@14.3.0: + optional: true + + syncpack@14.3.0: + optionalDependencies: + syncpack-darwin-arm64: 14.3.0 + syncpack-darwin-x64: 14.3.0 + syncpack-linux-arm64: 14.3.0 + syncpack-linux-arm64-musl: 14.3.0 + syncpack-linux-x64: 14.3.0 + syncpack-linux-x64-musl: 14.3.0 + syncpack-windows-arm64: 14.3.0 + syncpack-windows-x64: 14.3.0 tapable@2.3.0: {} @@ -23523,7 +26581,7 @@ snapshots: pump: 3.0.3 tar-stream: 2.2.0 - tar-fs@3.1.1: + tar-fs@3.1.2: dependencies: pump: 3.0.3 tar-stream: 3.1.7 @@ -23552,6 +26610,16 @@ snapshots: - bare-abort-controller - react-native-b4a + tar@4.4.18: + dependencies: + chownr: 1.1.4 + fs-minipass: 1.2.7 + minipass: 2.9.0 + minizlib: 1.3.3 + mkdirp: 0.5.6 + safe-buffer: 5.2.1 + yallist: 3.1.1 + tar@7.5.2: dependencies: '@isaacs/fs-minipass': 4.0.1 @@ -23560,25 +26628,16 @@ snapshots: minizlib: 3.1.0 yallist: 5.0.0 - teeny-request@9.0.0(encoding@0.1.13): - dependencies: - http-proxy-agent: 5.0.0 - https-proxy-agent: 5.0.1 - node-fetch: 2.7.0(encoding@0.1.13) - stream-events: 1.0.5 - uuid: 9.0.1 - transitivePeerDependencies: - - encoding - - supports-color - - terser-webpack-plugin@5.3.16(webpack@5.104.1): + terser-webpack-plugin@5.3.16(@swc/core@1.15.24)(webpack@5.104.1(@swc/core@1.15.24)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 serialize-javascript: 6.0.2 terser: 5.44.1 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) + optionalDependencies: + '@swc/core': 1.15.24 terser@5.44.1: dependencies: @@ -23593,23 +26652,23 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 - testcontainers@11.11.0: + testcontainers@11.13.0: dependencies: '@balena/dockerignore': 1.0.2 - '@types/dockerode': 3.3.47 + '@types/dockerode': 4.0.1 archiver: 7.0.1 async-lock: 1.4.1 byline: 5.0.0 debug: 4.4.3 - docker-compose: 1.3.0 + docker-compose: 1.4.2 dockerode: 4.0.9 get-port: 7.1.0 proper-lockfile: 4.1.2 - properties-reader: 2.3.0 + properties-reader: 3.0.1 ssh-remote-port-forward: 1.0.4 - tar-fs: 3.1.1 + tar-fs: 3.1.2 tmp: 0.2.5 - undici: 7.16.0 + undici: 7.24.7 transitivePeerDependencies: - bare-abort-controller - bare-buffer @@ -23622,8 +26681,6 @@ snapshots: transitivePeerDependencies: - react-native-b4a - text-extensions@2.4.0: {} - text-table@0.2.0: {} thenify-all@1.6.0: @@ -23640,11 +26697,11 @@ snapshots: throttleit@2.1.0: {} - through@2.3.8: {} - thunky@1.1.0: {} - tightrope@0.2.0: {} + time-span@4.0.0: + dependencies: + convert-hrtime: 3.0.0 tiny-invariant@1.3.3: {} @@ -23654,6 +26711,8 @@ snapshots: tinyexec@1.0.2: {} + tinyexec@1.1.1: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -23661,8 +26720,6 @@ snapshots: tinypool@1.1.1: {} - tinyrainbow@3.0.3: {} - tldts-core@6.1.86: {} tldts@6.1.86: @@ -23677,6 +26734,8 @@ snapshots: dependencies: is-number: 7.0.0 + toidentifier@1.0.0: {} + toidentifier@1.0.1: {} toposort-class@1.0.1: {} @@ -23703,32 +26762,64 @@ snapshots: trough@2.2.0: {} - ts-api-utils@2.1.0(typescript@5.9.3): + ts-algebra@1.2.2: {} + + ts-api-utils@2.1.0(typescript@6.0.2): + dependencies: + typescript: 6.0.2 + + ts-api-utils@2.5.0(typescript@6.0.2): dependencies: - typescript: 5.9.3 + typescript: 6.0.2 ts-interface-checker@0.1.13: {} - ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3): + ts-morph@12.0.0: + dependencies: + '@ts-morph/common': 0.11.1 + code-block-writer: 10.1.1 + + ts-node@10.9.1(@swc/core@1.15.24)(@types/node@16.18.11)(typescript@4.9.5): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.12 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 25.0.3 + '@types/node': 16.18.11 acorn: 8.15.0 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.9.3 + typescript: 4.9.5 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - optional: true + optionalDependencies: + '@swc/core': 1.15.24 + + ts-node@10.9.2(@swc/core@1.15.24)(@types/node@25.5.2)(typescript@6.0.2): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.12 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 25.5.2 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 6.0.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optionalDependencies: + '@swc/core': 1.15.24 - ts-toolbelt@9.6.0: {} + ts-toolbelt@6.15.5: {} tsconfig-paths@3.15.0: dependencies: @@ -23743,7 +26834,7 @@ snapshots: tsscmp@1.0.6: {} - tsup@8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): + tsup@8.5.1(@swc/core@1.15.24)(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.3): dependencies: bundle-require: 5.1.0(esbuild@0.27.1) cac: 6.7.14 @@ -23754,7 +26845,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(yaml@2.8.2) + postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(yaml@2.8.3) resolve-from: 5.0.0 rollup: 4.53.3 source-map: 0.7.6 @@ -23763,8 +26854,9 @@ snapshots: tinyglobby: 0.2.15 tree-kill: 1.2.2 optionalDependencies: + '@swc/core': 1.15.24 postcss: 8.5.6 - typescript: 5.9.3 + typescript: 6.0.2 transitivePeerDependencies: - jiti - supports-color @@ -23778,32 +26870,14 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - turbo-darwin-64@2.6.3: - optional: true - - turbo-darwin-arm64@2.6.3: - optional: true - - turbo-linux-64@2.6.3: - optional: true - - turbo-linux-arm64@2.6.3: - optional: true - - turbo-windows-64@2.6.3: - optional: true - - turbo-windows-arm64@2.6.3: - optional: true - - turbo@2.6.3: + turbo@2.9.4: optionalDependencies: - turbo-darwin-64: 2.6.3 - turbo-darwin-arm64: 2.6.3 - turbo-linux-64: 2.6.3 - turbo-linux-arm64: 2.6.3 - turbo-windows-64: 2.6.3 - turbo-windows-arm64: 2.6.3 + '@turbo/darwin-64': 2.9.4 + '@turbo/darwin-arm64': 2.9.4 + '@turbo/linux-64': 2.9.4 + '@turbo/linux-arm64': 2.9.4 + '@turbo/windows-64': 2.9.4 + '@turbo/windows-arm64': 2.9.4 tweetnacl@0.14.5: {} @@ -23819,8 +26893,6 @@ snapshots: type-fest@2.19.0: {} - type-fest@4.41.0: {} - type-is@1.6.18: dependencies: media-typer: 0.3.0 @@ -23871,25 +26943,28 @@ snapshots: typedarray@0.0.6: {} - typescript-eslint@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2))(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/parser': 8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/typescript-estree': 8.58.0(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.0(eslint@9.39.2(jiti@2.6.1))(typescript@6.0.2) eslint: 9.39.2(jiti@2.6.1) - typescript: 5.9.3 + typescript: 6.0.2 transitivePeerDependencies: - supports-color - typescript@5.9.3: {} + typescript@4.9.5: {} + + typescript@6.0.2: {} ua-parser-js@1.0.41: {} ufo@1.6.1: {} - uglify-js@3.19.3: - optional: true + uglify-js@3.19.3: {} + + uid-promise@1.0.0: {} unbox-primitive@1.1.0: dependencies: @@ -23902,18 +26977,18 @@ snapshots: undici-types@6.21.0: {} - undici-types@7.16.0: {} + undici-types@7.18.2: {} + + undici@5.28.4: + dependencies: + '@fastify/busboy': 2.1.1 - undici@7.16.0: {} + undici@7.24.7: {} unicode-canonical-property-names-ecmascript@2.0.1: {} unicode-emoji-modifier-base@1.0.0: {} - unicode-emoji-utils@1.3.1: - dependencies: - emoji-regex-xs: 2.0.1 - unicode-match-property-ecmascript@2.0.0: dependencies: unicode-canonical-property-names-ecmascript: 2.0.1 @@ -23923,20 +26998,8 @@ snapshots: unicode-property-aliases-ecmascript@2.2.0: {} - unicorn-magic@0.1.0: {} - unicorn-magic@0.3.0: {} - unified@10.1.2: - dependencies: - '@types/unist': 2.0.11 - bail: 2.0.2 - extend: 3.0.2 - is-buffer: 2.0.5 - is-plain-obj: 4.1.0 - trough: 2.2.0 - vfile: 5.3.7 - unified@11.0.5: dependencies: '@types/unist': 3.0.3 @@ -23959,12 +27022,6 @@ snapshots: dependencies: crypto-random-string: 4.0.0 - unist-util-generated@2.0.1: {} - - unist-util-is@5.2.1: - dependencies: - '@types/unist': 2.0.11 - unist-util-is@6.0.1: dependencies: '@types/unist': 3.0.3 @@ -23973,38 +27030,19 @@ snapshots: dependencies: '@types/unist': 3.0.3 - unist-util-position@4.0.4: - dependencies: - '@types/unist': 2.0.11 - unist-util-position@5.0.0: dependencies: '@types/unist': 3.0.3 - unist-util-stringify-position@3.0.3: - dependencies: - '@types/unist': 2.0.11 - unist-util-stringify-position@4.0.0: dependencies: '@types/unist': 3.0.3 - unist-util-visit-parents@5.1.3: - dependencies: - '@types/unist': 2.0.11 - unist-util-is: 5.2.1 - unist-util-visit-parents@6.0.2: dependencies: '@types/unist': 3.0.3 unist-util-is: 6.0.1 - unist-util-visit@4.1.2: - dependencies: - '@types/unist': 2.0.11 - unist-util-is: 5.2.1 - unist-util-visit-parents: 5.1.3 - unist-util-visit@5.0.0: dependencies: '@types/unist': 3.0.3 @@ -24013,6 +27051,8 @@ snapshots: universal-user-agent@7.0.3: {} + universalify@0.1.2: {} + universalify@2.0.1: {} unpipe@1.0.0: {} @@ -24060,7 +27100,7 @@ snapshots: is-yarn-global: 0.4.1 latest-version: 7.0.0 pupa: 3.3.0 - semver: 7.7.3 + semver: 7.7.4 semver-diff: 4.0.0 xdg-basedir: 5.1.0 @@ -24070,14 +27110,14 @@ snapshots: urijs@1.19.11: {} - url-loader@4.1.1(file-loader@6.2.0(webpack@5.104.1))(webpack@5.104.1): + url-loader@4.1.1(file-loader@6.2.0(webpack@5.104.1(@swc/core@1.15.24)))(webpack@5.104.1(@swc/core@1.15.24)): dependencies: loader-utils: 2.0.4 mime-types: 2.1.35 schema-utils: 3.3.0 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) optionalDependencies: - file-loader: 6.2.0(webpack@5.104.1) + file-loader: 6.2.0(webpack@5.104.1(@swc/core@1.15.24)) url@0.10.3: dependencies: @@ -24087,22 +27127,18 @@ snapshots: url@0.11.4: dependencies: punycode: 1.4.1 - qs: 6.14.0 + qs: 6.14.1 - use-editable@2.3.3(react@19.2.3): + use-editable@2.3.3(react@19.2.5): dependencies: - react: 19.2.3 + react: 19.2.5 - use-sync-external-store@1.6.0(react@19.2.3): + use-sync-external-store@1.6.0(react@19.2.5): dependencies: - react: 19.2.3 + react: 19.2.5 util-deprecate@1.0.2: {} - util@0.10.4: - dependencies: - inherits: 2.0.3 - util@0.12.5: dependencies: inherits: 2.0.4 @@ -24119,23 +27155,13 @@ snapshots: uuid@10.0.0: {} - uuid@13.0.0: {} + uuid@3.3.2: {} uuid@8.0.0: {} uuid@8.3.2: {} - uuid@9.0.1: {} - - uvu@0.5.6: - dependencies: - dequal: 2.0.3 - diff: 5.2.0 - kleur: 4.1.5 - sade: 1.8.1 - - v8-compile-cache-lib@3.0.1: - optional: true + v8-compile-cache-lib@3.0.1: {} v8-to-istanbul@9.3.0: dependencies: @@ -24152,8 +27178,6 @@ snapshots: dependencies: builtins: 1.0.3 - validate-npm-package-name@6.0.2: {} - validate-npm-package-name@7.0.0: {} validate.io-array@1.0.6: {} @@ -24177,33 +27201,37 @@ snapshots: vary@1.1.2: {} - vfile-location@4.1.0: - dependencies: - '@types/unist': 2.0.11 - vfile: 5.3.7 + vercel@39.4.2(@swc/core@1.15.24)(encoding@0.1.13)(rollup@4.53.3): + dependencies: + '@vercel/build-utils': 9.1.0 + '@vercel/fun': 1.1.2(encoding@0.1.13) + '@vercel/go': 3.2.1 + '@vercel/hydrogen': 1.0.11 + '@vercel/next': 4.4.4(encoding@0.1.13)(rollup@4.53.3) + '@vercel/node': 5.0.4(@swc/core@1.15.24)(encoding@0.1.13)(rollup@4.53.3) + '@vercel/python': 4.7.1 + '@vercel/redwood': 2.1.13(encoding@0.1.13)(rollup@4.53.3) + '@vercel/remix-builder': 5.1.1(encoding@0.1.13)(rollup@4.53.3) + '@vercel/ruby': 2.2.0 + '@vercel/static-build': 2.5.43 + chokidar: 4.0.0 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - encoding + - rollup + - supports-color vfile-location@5.0.3: dependencies: '@types/unist': 3.0.3 vfile: 6.0.3 - vfile-message@3.1.4: - dependencies: - '@types/unist': 2.0.11 - unist-util-stringify-position: 3.0.3 - vfile-message@4.0.3: dependencies: '@types/unist': 3.0.3 unist-util-stringify-position: 4.0.0 - vfile@5.3.7: - dependencies: - '@types/unist': 2.0.11 - is-buffer: 2.0.5 - unist-util-stringify-position: 3.0.3 - vfile-message: 3.1.4 - vfile@6.0.3: dependencies: '@types/unist': 3.0.3 @@ -24238,6 +27266,8 @@ snapshots: web-namespaces@2.0.1: {} + web-vitals@0.2.4: {} + webidl-conversions@3.0.1: {} webidl-conversions@7.0.0: {} @@ -24260,7 +27290,7 @@ snapshots: - bufferutil - utf-8-validate - webpack-dev-middleware@7.4.5(webpack@5.104.1): + webpack-dev-middleware@7.4.5(webpack@5.104.1(@swc/core@1.15.24)): dependencies: colorette: 2.0.20 memfs: 4.51.1 @@ -24269,9 +27299,9 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.3 optionalDependencies: - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) - webpack-dev-server@5.2.2(webpack@5.104.1): + webpack-dev-server@5.2.2(webpack@5.104.1(@swc/core@1.15.24)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -24299,10 +27329,10 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.5(webpack@5.104.1) + webpack-dev-middleware: 7.4.5(webpack@5.104.1(@swc/core@1.15.24)) ws: 8.18.3 optionalDependencies: - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) transitivePeerDependencies: - bufferutil - debug @@ -24323,7 +27353,7 @@ snapshots: webpack-sources@3.3.3: {} - webpack@5.104.1: + webpack@5.104.1(@swc/core@1.15.24): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -24347,7 +27377,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.0 - terser-webpack-plugin: 5.3.16(webpack@5.104.1) + terser-webpack-plugin: 5.3.16(@swc/core@1.15.24)(webpack@5.104.1(@swc/core@1.15.24)) watchpack: 2.5.0 webpack-sources: 3.3.3 transitivePeerDependencies: @@ -24355,7 +27385,7 @@ snapshots: - esbuild - uglify-js - webpackbar@6.0.1(webpack@5.104.1): + webpackbar@6.0.1(webpack@5.104.1(@swc/core@1.15.24)): dependencies: ansi-escapes: 4.3.2 chalk: 4.1.2 @@ -24364,7 +27394,7 @@ snapshots: markdown-table: 2.0.0 pretty-time: 1.1.0 std-env: 3.10.0 - webpack: 5.104.1 + webpack: 5.104.1(@swc/core@1.15.24) wrap-ansi: 7.0.0 websocket-driver@0.7.4: @@ -24434,6 +27464,10 @@ snapshots: gopd: 1.2.0 has-tostringtag: 1.0.2 + which@1.3.1: + dependencies: + isexe: 2.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -24454,18 +27488,12 @@ snapshots: wkx@0.5.0: dependencies: - '@types/node': 25.0.3 + '@types/node': 25.5.2 word-wrap@1.2.5: {} wordwrap@1.0.0: {} - wrap-ansi@6.2.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -24503,18 +27531,10 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 - write-file-atomic@7.0.0: + write-file-atomic@7.0.1: dependencies: - imurmurhash: 0.1.4 signal-exit: 4.1.0 - write-json-file@6.0.0: - dependencies: - detect-indent: 7.0.2 - is-plain-obj: 4.1.0 - sort-keys: 5.1.0 - write-file-atomic: 5.0.1 - write-json-file@7.0.0: dependencies: detect-indent: 7.0.2 @@ -24522,14 +27542,6 @@ snapshots: sort-keys: 6.0.0 write-file-atomic: 6.0.0 - write-package@7.2.0: - dependencies: - deepmerge-ts: 7.1.5 - read-pkg: 9.0.1 - sort-keys: 5.1.0 - type-fest: 4.41.0 - write-json-file: 6.0.0 - ws@7.5.10: {} ws@8.18.3: {} @@ -24538,11 +27550,19 @@ snapshots: dependencies: is-wsl: 3.1.0 + xdg-app-paths@5.1.0: + dependencies: + xdg-portable: 7.3.0 + xdg-basedir@5.1.0: {} - xml-formatter@2.6.1: + xdg-portable@7.3.0: + dependencies: + os-paths: 4.4.0 + + xml-formatter@3.7.0: dependencies: - xml-parser-xo: 3.2.0 + xml-parser-xo: 4.1.5 xml-js@1.6.11: dependencies: @@ -24550,11 +27570,11 @@ snapshots: xml-name-validator@5.0.0: {} - xml-parser-xo@3.2.0: {} + xml-parser-xo@4.1.5: {} xml2js@0.6.2: dependencies: - sax: 1.2.1 + sax: 1.4.3 xmlbuilder: 11.0.1 xmlbuilder@11.0.1: {} @@ -24577,6 +27597,8 @@ snapshots: yaml@2.8.2: {} + yaml@2.8.3: {} + yargs-parser@21.1.1: {} yargs-parser@22.0.0: {} @@ -24600,15 +27622,26 @@ snapshots: y18n: 5.0.8 yargs-parser: 22.0.0 - yn@3.1.1: - optional: true + yauzl-clone@1.0.4: + dependencies: + events-intercept: 2.0.0 + + yauzl-promise@2.1.3: + dependencies: + yauzl: 2.10.0 + yauzl-clone: 1.0.4 + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + yn@3.1.1: {} yocto-queue@0.1.0: {} yocto-queue@1.2.2: {} - yoctocolors-cjs@2.1.3: {} - yoctocolors@2.1.2: {} zeptomatch@2.1.0: @@ -24622,7 +27655,7 @@ snapshots: compress-commons: 6.0.2 readable-stream: 4.7.0 - zod-to-json-schema@3.25.0(zod@3.25.76): + zod-to-json-schema@3.25.2(zod@3.25.76): dependencies: zod: 3.25.76 diff --git a/tests/smoke-test.sh b/tests/smoke-test.sh new file mode 100755 index 00000000..3b044145 --- /dev/null +++ b/tests/smoke-test.sh @@ -0,0 +1,189 @@ +#!/bin/sh +set -e + +BASE_URL="${SERVER_URL:-http://localhost:50477}/api/v1" + +echo "=== Smoke test started ===" + +# 1. Bootstrap admin user (201 on first run, 409 if already exists) +echo "--- Bootstrapping admin user ---" +BOOTSTRAP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE_URL/users/bootstrap" \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"Admin1234!"}') +if [ "$BOOTSTRAP_STATUS" != "201" ] && [ "$BOOTSTRAP_STATUS" != "409" ]; then + echo "ERROR: Bootstrap returned $BOOTSTRAP_STATUS" >&2 + exit 1 +fi +echo "Bootstrap status: $BOOTSTRAP_STATUS" + +# 2. Login to get JWT token +echo "--- Logging in ---" +LOGIN_RESP=$(curl -sf -X POST "$BASE_URL/users/login" \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"Admin1234!"}') +TOKEN=$(echo "$LOGIN_RESP" | jq -r '.token') +echo "Token: $(echo "$TOKEN" | cut -c1-20)..." + +# 3. Create a project +echo "--- Creating project ---" +PROJECT_RESP=$(curl -sf -X POST "$BASE_URL/projects" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + -d '{"name":"smoke-test-project"}') +PROJECT_PUBLIC_ID=$(echo "$PROJECT_RESP" | jq -r '.id') +echo "Project id: $PROJECT_PUBLIC_ID" + +# 4. Upload a file via multipart form +echo "--- Uploading file ---" +echo "Hello, smoke test!" > /tmp/smoke.txt +UPLOAD_RESP=$(curl -sf -X POST "$BASE_URL/files/upload" \ + -H "Authorization: Bearer $TOKEN" \ + -F "file=@/tmp/smoke.txt;type=text/plain" \ + -F "projectId=$PROJECT_PUBLIC_ID") +FILE_ID=$(echo "$UPLOAD_RESP" | jq -r '.id') +echo "File id: $FILE_ID" + +# 5. Get file metadata +echo "--- Getting file metadata ---" +GET_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/files/$FILE_ID" \ + -H "Authorization: Bearer $TOKEN") +if [ "$GET_STATUS" != "200" ]; then + echo "ERROR: GET file returned $GET_STATUS, expected 200" >&2 + exit 1 +fi +echo "GET status: $GET_STATUS" + +# 6. Download file and verify content +echo "--- Downloading file ---" +CONTENT=$(curl -sf "$BASE_URL/files/$FILE_ID/download" \ + -H "Authorization: Bearer $TOKEN") +EXPECTED="Hello, smoke test!" +if [ "$CONTENT" != "$EXPECTED" ]; then + echo "ERROR: Content mismatch. Got '$CONTENT', expected '$EXPECTED'" >&2 + exit 1 +fi +echo "Content matches." + +# 7. Update metadata +echo "--- Updating metadata ---" +PATCH_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X PATCH "$BASE_URL/files/$FILE_ID/metadata" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + -d '{"metadata":"smoke-tested"}') +if [ "$PATCH_STATUS" != "200" ]; then + echo "ERROR: PATCH metadata returned $PATCH_STATUS, expected 200" >&2 + exit 1 +fi +echo "PATCH status: $PATCH_STATUS" + +# 8. Delete file +echo "--- Deleting file ---" +DELETE_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE "$BASE_URL/files/$FILE_ID" \ + -H "Authorization: Bearer $TOKEN") +if [ "$DELETE_STATUS" != "204" ]; then + echo "ERROR: DELETE returned $DELETE_STATUS, expected 204" >&2 + exit 1 +fi +echo "DELETE status: $DELETE_STATUS" + +# 9. Verify file is gone (404) +echo "--- Verifying deletion ---" +AFTER_DELETE_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/files/$FILE_ID" \ + -H "Authorization: Bearer $TOKEN") +if [ "$AFTER_DELETE_STATUS" != "404" ]; then + echo "ERROR: Expected 404 after deletion, got $AFTER_DELETE_STATUS" >&2 + exit 1 +fi +echo "File correctly returns 404 after deletion." + +# 10. Create first document +echo "--- Creating first document ---" +DOC1_RESP=$(curl -sf -X POST "$BASE_URL/documents" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + -d "{\"projectId\":\"$PROJECT_PUBLIC_ID\",\"content\":\"The quick brown fox jumps over the lazy dog\",\"filename\":\"fox.txt\"}") +DOC1_ID=$(echo "$DOC1_RESP" | jq -r '.id') +echo "Document 1 id: $DOC1_ID" + +# 11. Create second document +echo "--- Creating second document ---" +DOC2_RESP=$(curl -sf -X POST "$BASE_URL/documents" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + -d "{\"projectId\":\"$PROJECT_PUBLIC_ID\",\"content\":\"Machine learning models require large amounts of training data\",\"filename\":\"ml.txt\"}") +DOC2_ID=$(echo "$DOC2_RESP" | jq -r '.id') +echo "Document 2 id: $DOC2_ID" + +# 12. Search documents +echo "--- Searching documents ---" +SEARCH_RESP=$(curl -sf -X POST "$BASE_URL/documents/search" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + -d "{\"projectId\":\"$PROJECT_PUBLIC_ID\",\"query\":\"fox animal jumping\",\"limit\":5}") +SEARCH_COUNT=$(echo "$SEARCH_RESP" | jq 'length') +if [ "$SEARCH_COUNT" -lt 1 ]; then + echo "ERROR: Document search returned $SEARCH_COUNT results, expected at least 1" >&2 + exit 1 +fi +echo "Search returned $SEARCH_COUNT result(s)." + +# 13. Delete documents +echo "--- Deleting documents ---" +DELETE_DOC1=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE "$BASE_URL/documents/$DOC1_ID" \ + -H "Authorization: Bearer $TOKEN") +if [ "$DELETE_DOC1" != "204" ]; then + echo "ERROR: DELETE document 1 returned $DELETE_DOC1, expected 204" >&2 + exit 1 +fi +DELETE_DOC2=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE "$BASE_URL/documents/$DOC2_ID" \ + -H "Authorization: Bearer $TOKEN") +if [ "$DELETE_DOC2" != "204" ]; then + echo "ERROR: DELETE document 2 returned $DELETE_DOC2, expected 204" >&2 + exit 1 +fi +echo "Documents deleted." + +# 14. Agent SSE stream — 401 without auth +echo "--- Agent SSE stream: 401 without auth ---" +AGENT_UNAUTH=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE_URL/agents/run/stream" \ + -H "Content-Type: application/json" \ + -d '{"prompt":"hello"}') +if [ "$AGENT_UNAUTH" != "401" ]; then + echo "ERROR: Expected 401, got $AGENT_UNAUTH" >&2 + exit 1 +fi +echo "401 without auth: OK" + +# 15. Agent SSE stream — 400 without prompt +echo "--- Agent SSE stream: 400 without prompt ---" +AGENT_NOPROMPT=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE_URL/agents/run/stream" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + -d '{}') +if [ "$AGENT_NOPROMPT" != "400" ]; then + echo "ERROR: Expected 400, got $AGENT_NOPROMPT" >&2 + exit 1 +fi +echo "400 without prompt: OK" + +# 16. Agent SSE stream — valid request +echo "--- Agent SSE stream: valid request ---" +AGENT_STATUS=$(curl -s -o /tmp/agent_sse.txt -w "%{http_code}" -X POST "$BASE_URL/agents/run/stream" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + -d '{"prompt":"tell me a joke"}') +if [ "$AGENT_STATUS" != "200" ]; then + echo "ERROR: Agent stream returned $AGENT_STATUS, expected 200" >&2 + exit 1 +fi +if ! grep -q "event: done" /tmp/agent_sse.txt; then + echo "ERROR: Agent stream missing 'event: done'" >&2 + cat /tmp/agent_sse.txt >&2 + exit 1 +fi +echo "Agent SSE stream OK." +echo "--- Agent SSE stream output ---" +cat /tmp/agent_sse.txt + +echo "" +echo "=== All smoke tests passed! ===" diff --git a/turbo.json b/turbo.json index 209a48a3..61e5b81d 100644 --- a/turbo.json +++ b/turbo.json @@ -1,10 +1,21 @@ { - "$schema": "https://turborepo.org/schema.json", - "globalEnv": ["DATABASE_*"], + "$schema": "./node_modules/turbo/schema.json", "tasks": { "build": { "dependsOn": ["^build"], "outputs": ["dist/**", "build/**"] + }, + "test": { + "dependsOn": ["^build"], + "outputs": [] + }, + "deploy": { + "dependsOn": ["build", "test"], + "outputs": [] + }, + "deploy-report": { + "dependsOn": ["deploy"], + "outputs": [] } } }