diff --git a/.changeset/bright-permissions-unlocked.md b/.changeset/bright-permissions-unlocked.md new file mode 100644 index 0000000..3f7c0f2 --- /dev/null +++ b/.changeset/bright-permissions-unlocked.md @@ -0,0 +1,17 @@ +--- +'thatopen-services': minor +--- + +Align the client with the platform's new project-scoped permissions model and split the client surface for apps vs components. + +**New: `PlatformClient`.** Extends `EngineServicesClient` with a bearer-only constructor. Use it from apps, frontends, and any caller authenticating with a user JWT. On top of the inherited API-token-compatible surface, `PlatformClient` owns the JWT-only routes `getProject`, `getProjectData`, `checkPermission`, and `checkPermissionBatch` — those hit `ProjectController` on the backend which is guarded by JWT, so they're not reachable from an access token. `EngineServicesClient` remains the right choice for components (API-token auth, local server, WebSocket progress). + +The `PlatformClient` constructor accepts either a static JWT string **or a provider function** (`() => string | Promise`) that's called on every request — so Auth0's `getAccessTokenSilently()` and similar refreshing sources can be passed directly and expired tokens never stick. `PlatformClient.fromPlatformContext()` is available as a static factory for apps running inside the platform iframe. + +**Project-scoped listings on the main list methods.** `listFiles`, `listFolders`, `listApps`, and `listComponents` now accept an optional `projectId` and forward it to the new public `GET /item?projectId=X` / `GET /item/folder?projectId=X` routes. Per-entity role overrides are applied server-side; callers without project role permission get 403 (not an empty list). Pass `itemType: 'APP' | 'TOOL' | 'FILE'` to switch what comes back. + +**Updated permission checks.** `checkPermission` now returns `{ hasPermission, scope }` where `scope` is `'global' | 'project' | 'entity' | 'none'`. New `checkPermissionBatch(checks)` evaluates multiple checks in one round-trip. + +**Execution scoping.** `executeComponent` accepts `projectId` as a reserved key on `executionParams`; foreign project ids are rejected by the backend. `listExecutions(componentId, projectId?)` forwards the query param. + +**Breaking.** The v1 convenience helpers `listProjectFiles`, `listProjectFolders`, `listProjectApps`, `listProjectComponents` are removed. They pointed at JWT-only `/project/:id/*` routes, which was the wrong target for an API-token client. Replace with `listFiles({ projectId })` / `listFolders({ projectId })` / `listApps({ projectId })` / `listComponents({ projectId })`. diff --git a/CONTEXT.md b/CONTEXT.md index 0aedd33..98b8ba7 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -45,6 +45,8 @@ npm run build:cli # CLI only ### Testing ```bash +npm run test # Run vitest unit tests (HTTP client contract) +npm run test:watch # Vitest watch mode npm run test:ui # Interactive browser test page npm run test:cli-build-app # Scaffold + build a test app npm run test:cli-build-component # Scaffold + build a test cloud component @@ -53,6 +55,77 @@ npm run test:cli-build-tests # Build CLI + scaffold test app & test compone npm run test:cli-serve-tests # Serve the test app and test component's local server in parallel ``` +## Two clients — components vs apps/FE + +This package ships two clients with overlapping but intentionally different +surfaces. Pick the one that matches who's calling. + +### `EngineServicesClient` +**For cloud components running inside the platform.** Authenticates via +API token by default (`?accessToken=`). Supports local-server execution, +WebSocket execution progress, built-in component runtime helpers, and the +low-level HTTP surface. This is what you get via +`EngineServicesClient.fromPlatformContext()` inside a component bundle. + +### `PlatformClient` +**For apps, frontends, and any caller using a user JWT.** Extends +`EngineServicesClient`; the API-token-compatible surface is inherited and +the constructor forces `useBearer: true`. On top, it owns the JWT-only +routes — `getProject`, `getProjectData`, `checkPermission`, +`checkPermissionBatch` — which hit `ProjectController` in the backend +(guarded by JWT) and are not reachable with an access token. + +The constructor accepts either a static JWT or a provider function +(sync or async) that returns the current JWT. The provider is called on +every request, so Auth0's `getAccessTokenSilently()` and similar +refreshing sources Just Work: + +```ts +import { PlatformClient } from 'thatopen-services'; +const client = new PlatformClient( + () => auth0.getAccessTokenSilently(), + 'https://api.thatopen.com', +); +await client.getProjectData(projectId); +``` + +`PlatformClient.fromPlatformContext()` is available for apps running inside +the platform iframe — it pulls the JWT from +`window.__THATOPEN_CONTEXT__` and returns a ready-to-use client. + +Choose by audience: +- Component code → `EngineServicesClient` (or `EngineServicesClient.fromPlatformContext()`). +- App / FE / integration with a user JWT → `PlatformClient`. + +## Permissions contract (backend coupling) + +The platform API enforces **project-scoped permission checks**: whenever a +request carries a `projectId` (URL param, query, or body), the backend +rejects the call if the resource does not belong to that project or the +caller lacks permission there — regardless of whether the caller has access +to the same resource in a different project. + +Relevant client methods: + +- `executeComponent(componentId, executionParams, versionTag?)`: include + `projectId` in `executionParams` to scope the execution. The backend + validates that the component is linked to that project. Without a + `projectId`, the execution runs in the user's personal/ownership scope. +- `listExecutions(componentId, projectId?)`: pass `projectId` to filter + executions to that project's context. +- `checkPermission({ resourceType, action, resourceId?, projectId? })`: + returns `{ hasPermission, scope }`. `scope` is `'global' | 'project' | + 'entity' | 'none'` — `global` for admin/owner bypass, `project` for a + role broad grant, `entity` for a per-entity override, `none` for denied. +- `checkPermissionBatch(checks)`: evaluates a list of checks in one + round-trip. Useful for hydrating action visibility for many rows without + N+1 calls. + +Per-entity permission overrides (`ResourcePermission.removePermission`, +`ResourcePermission.appliesToDescendants`) are applied automatically on the +server side when listing project files/folders — no client change needed. + + ### Publishing to npm ```bash diff --git a/package.json b/package.json index e24a179..6a42034 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "lint": "eslint src/", "build": "eslint src/ && tsc && vite build && vite build --config vite.config.cli.mts", "build:lib": "tsc && vite build", + "test": "vitest run", + "test:watch": "vitest", "build:cli": "vite build --config vite.config.cli.mts && node scripts/copy-templates.mjs", "preview": "vite preview", "test:ui": "vite --config test/vite.config.mts", @@ -35,9 +37,9 @@ }, "devDependencies": { "@changesets/cli": "^2.27.7", - "@thatopen/fragments": "~3.4.0", "@thatopen/components": "~3.4.0", "@thatopen/components-front": "~3.4.0", + "@thatopen/fragments": "~3.4.0", "@thatopen/ui": "~3.4.0", "@thatopen/ui-obc": "~3.4.0", "@types/node": "^20.12.12", @@ -56,7 +58,8 @@ "typescript-eslint": "^7.10.0", "vite": "^5.2.0", "vite-plugin-dts": "^3.9.1", - "vite-plugin-eslint": "^1.8.1" + "vite-plugin-eslint": "^1.8.1", + "vitest": "^4.1.4" }, "dependencies": { "dotenv": "^16.4.5", @@ -79,4 +82,4 @@ "optional": true } } -} \ No newline at end of file +} diff --git a/src/core/client.test.ts b/src/core/client.test.ts new file mode 100644 index 0000000..74bf5a1 --- /dev/null +++ b/src/core/client.test.ts @@ -0,0 +1,228 @@ +import { + describe, + it, + expect, + beforeEach, + afterEach, + vi, + type Mock, +} from 'vitest'; +import { EngineServicesClient } from './client'; + +const API = 'https://api.example.com'; +const TOKEN = 'test-token'; + +function okResponse(data: unknown): Response { + return { + ok: true, + status: 200, + statusText: 'OK', + text: async () => JSON.stringify(data), + json: async () => data, + } as unknown as Response; +} + +function errorResponse(status: number, message = 'Bad Request'): Response { + return { + ok: false, + status, + statusText: message, + text: async () => message, + json: async () => ({ message }), + } as unknown as Response; +} + +function getCall( + fetchMock: Mock, + index = 0, +): { url: string; init: RequestInit } { + const call = fetchMock.mock.calls[index]; + return { url: call[0] as string, init: call[1] as RequestInit }; +} + +function parseUrl(url: string): { pathname: string; params: URLSearchParams } { + const u = new URL(url); + return { pathname: u.pathname, params: u.searchParams }; +} + +describe('EngineServicesClient — HTTP contract', () => { + let fetchMock: Mock; + + beforeEach(() => { + fetchMock = vi.fn(); + globalThis.fetch = fetchMock as unknown as typeof fetch; + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('auth mode', () => { + it('access-token mode puts token in query string', async () => { + fetchMock.mockResolvedValue(okResponse([])); + const client = new EngineServicesClient(TOKEN, API); + await client.listFiles(); + const { url, init } = getCall(fetchMock); + const { params } = parseUrl(url); + expect(params.get('accessToken')).toBe(TOKEN); + expect( + (init.headers as Record).Authorization, + ).toBeUndefined(); + }); + + it('bearer mode sets Authorization header and omits accessToken query param', async () => { + fetchMock.mockResolvedValue(okResponse([])); + const client = new EngineServicesClient(TOKEN, API, { useBearer: true }); + await client.listFiles(); + const { url, init } = getCall(fetchMock); + const { params } = parseUrl(url); + expect(params.get('accessToken')).toBeNull(); + expect((init.headers as Record).Authorization).toBe( + `Bearer ${TOKEN}`, + ); + }); + }); + + describe('executeComponent', () => { + it('POSTs to /processor/:id/execute with JSON body including projectId when supplied', async () => { + fetchMock.mockResolvedValue(okResponse({ executionId: 'exec-1' })); + const client = new EngineServicesClient(TOKEN, API); + const result = await client.executeComponent( + 'comp-42', + { projectId: 'proj-99', foo: 'bar' }, + 'v1', + ); + expect(result).toEqual({ executionId: 'exec-1' }); + const { url, init } = getCall(fetchMock); + const { pathname, params } = parseUrl(url); + expect(pathname).toBe('/api/processor/comp-42/execute'); + expect(init.method).toBe('POST'); + expect(params.get('versionTag')).toBe('v1'); + expect(init.body).toBe( + JSON.stringify({ projectId: 'proj-99', foo: 'bar' }), + ); + }); + + it('omits versionTag from query when not supplied', async () => { + fetchMock.mockResolvedValue(okResponse({ executionId: 'exec-2' })); + const client = new EngineServicesClient(TOKEN, API); + await client.executeComponent('comp-42', {}); + const { url } = getCall(fetchMock); + const { params } = parseUrl(url); + expect(params.get('versionTag')).toBeNull(); + }); + }); + + describe('listExecutions', () => { + it('passes projectId as a query parameter when provided', async () => { + fetchMock.mockResolvedValue(okResponse([])); + const client = new EngineServicesClient(TOKEN, API); + await client.listExecutions('comp-1', 'proj-1'); + const { url } = getCall(fetchMock); + const { pathname, params } = parseUrl(url); + expect(pathname).toBe('/api/processor/comp-1/progress'); + expect(params.get('projectId')).toBe('proj-1'); + }); + + it('omits projectId when not supplied', async () => { + fetchMock.mockResolvedValue(okResponse([])); + const client = new EngineServicesClient(TOKEN, API); + await client.listExecutions('comp-1'); + const { url } = getCall(fetchMock); + const { params } = parseUrl(url); + expect(params.get('projectId')).toBeNull(); + }); + }); + + // `checkPermission` and `checkPermissionBatch` live on `PlatformClient` + // (JWT-only routes) — their contract tests are in `platform-client.test.ts`. + + describe('project-scoped list methods — via projectId query on /item and /item/folder', () => { + it('listFiles({ projectId }) forwards projectId on /item', async () => { + fetchMock.mockResolvedValue(okResponse([])); + const client = new EngineServicesClient(TOKEN, API); + await client.listFiles({ projectId: 'proj-1', archived: true }); + const { url, init } = getCall(fetchMock); + const { pathname, params } = parseUrl(url); + expect(pathname).toBe('/api/item'); + expect(init.method).toBe('GET'); + expect(params.get('itemType')).toBe('FILE'); + expect(params.get('projectId')).toBe('proj-1'); + expect(params.get('archived')).toBe('true'); + }); + + it('listFolders({ projectId }) forwards projectId on /item/folder', async () => { + fetchMock.mockResolvedValue(okResponse([])); + const client = new EngineServicesClient(TOKEN, API); + await client.listFolders({ projectId: 'proj-1' }); + const { url, init } = getCall(fetchMock); + const { pathname, params } = parseUrl(url); + expect(pathname).toBe('/api/item/folder'); + expect(init.method).toBe('GET'); + expect(params.get('projectId')).toBe('proj-1'); + }); + + it('listApps({ projectId }) forwards projectId on /item', async () => { + fetchMock.mockResolvedValue(okResponse([])); + const client = new EngineServicesClient(TOKEN, API); + await client.listApps({ projectId: 'proj-1' }); + const { url, params } = { + ...getCall(fetchMock), + ...parseUrl(getCall(fetchMock).url), + }; + expect(url).toMatch(/\/api\/item\b/); + expect(params.get('itemType')).toBe('APP'); + expect(params.get('projectId')).toBe('proj-1'); + }); + + it('listComponents({ projectId }) forwards projectId on /item', async () => { + fetchMock.mockResolvedValue(okResponse([])); + const client = new EngineServicesClient(TOKEN, API); + await client.listComponents({ projectId: 'proj-1' }); + const { params } = parseUrl(getCall(fetchMock).url); + expect(params.get('itemType')).toBe('TOOL'); + expect(params.get('projectId')).toBe('proj-1'); + }); + }); + + describe('createFile / createFolder / createComponent / createApp pass projectId', () => { + it('createFolder POSTs projectId in JSON body', async () => { + fetchMock.mockResolvedValue(okResponse({})); + const client = new EngineServicesClient(TOKEN, API); + await client.createFolder('My folder', undefined, 'proj-1'); + const { url, init } = getCall(fetchMock); + const { pathname } = parseUrl(url); + expect(pathname).toBe('/api/item/folder'); + expect(init.method).toBe('POST'); + const body = JSON.parse(init.body as string); + expect(body).toMatchObject({ name: 'My folder', projectId: 'proj-1' }); + }); + + it('createFile attaches projectId to the FormData body', async () => { + fetchMock.mockResolvedValue(okResponse({})); + const client = new EngineServicesClient(TOKEN, API); + const file = new Blob(['dummy']) as Blob; + await client.createFile({ + file, + name: 'doc.ifc', + versionTag: 'v1', + projectId: 'proj-1', + }); + const { init } = getCall(fetchMock); + const formData = init.body as FormData; + expect(formData).toBeInstanceOf(FormData); + expect(formData.get('projectId')).toBe('proj-1'); + expect(formData.get('itemType')).toBe('FILE'); + }); + }); + + describe('error handling', () => { + it('throws when the server responds with a non-2xx status', async () => { + fetchMock.mockResolvedValue(errorResponse(403, 'Forbidden')); + const client = new EngineServicesClient(TOKEN, API); + await expect( + client.executeComponent('comp-1', { projectId: 'foreign' }), + ).rejects.toThrow(/403/); + }); + }); +}); diff --git a/src/core/client.ts b/src/core/client.ts index ecde847..2056b43 100644 --- a/src/core/client.ts +++ b/src/core/client.ts @@ -17,7 +17,6 @@ import { } from '../types/items'; import { CreateItemResponse, UpdateItemResponse } from '../types/response'; import { CreateHiddenItemResult, HiddenFileEntity } from '../types/files'; -import { Project, ProjectData } from '../types/projects'; import { ThatOpenContext } from '../types/context'; declare global { @@ -30,7 +29,6 @@ declare global { const FOLDER_PATH = 'item/folder'; const ITEM_PATH = 'item'; const PROCESS_PATH = 'processor'; -const PROJECT_PATH = 'project'; const HIDDEN_PATH = 'hidden'; const ITEM_TYPE_FILE = 'FILE'; const ITEM_TYPE_COMPONENT = 'TOOL'; @@ -284,6 +282,19 @@ export class EngineServicesClient { return `${this.apiUrl}/${path}`; } + /** + * Protected extension point for subclasses that need dynamic tokens + * (e.g. `PlatformClient` with an auth provider callback). The default + * returns the static token captured at construction time. + * + * When a subclass overrides this to call an async refresh function, + * the new token is picked up on every request — expired tokens no + * longer stick around. + */ + protected async resolveAccessToken(): Promise { + return this.accessToken; + } + async #requestApi( method: string, path: string, @@ -301,10 +312,11 @@ export class EngineServicesClient { const url = this.#buildUrl(path); const cleanQuery = this.#cleanData(query); + const token = await this.resolveAccessToken(); const params = { ...cleanQuery, - ...(this.useBearer ? {} : { accessToken: this.accessToken }), + ...(this.useBearer ? {} : { accessToken: token }), }; try { @@ -315,7 +327,7 @@ export class EngineServicesClient { headers: { Accept: 'application/json', ...(contentType && { 'Content-Type': contentType }), - ...(this.useBearer && { Authorization: `Bearer ${this.accessToken}` }), + ...(this.useBearer && { Authorization: `Bearer ${token}` }), }, ...(body && { body }), }, @@ -348,19 +360,42 @@ export class EngineServicesClient { } } + /** + * Protected extension hook for subclasses (e.g. `PlatformClient`) that + * need to add HTTP methods against additional backend routes. Delegates + * to the private `#requestApi` implementation so retry / auth / query- + * cleaning logic is applied identically. + */ + protected async request( + method: string, + path: string, + requestData?: { + body?: BodyInit; + query?: object; + contentType?: + | 'application/json' + | 'multipart/form-data' + | 'application/x-www-form-urlencoded'; + retries?: number; + }, + ): Promise { + return this.#requestApi(method, path, requestData); + } + async #requestFile(path: string, requestData?: { query?: object }) { const { query } = requestData || {}; const url = this.#buildUrl(path); + const token = await this.resolveAccessToken(); const params = { ...query, - ...(this.useBearer ? {} : { accessToken: this.accessToken }), + ...(this.useBearer ? {} : { accessToken: token }), }; const response = await fetch( url + '?' + new URLSearchParams(params).toString(), { method: 'GET', ...(this.useBearer && { - headers: { Authorization: `Bearer ${this.accessToken}` }, + headers: { Authorization: `Bearer ${token}` }, }), }, ); @@ -375,8 +410,17 @@ export class EngineServicesClient { * @param filters - Optional filters for folder and archive status. * @returns Array of file items. */ - async listFiles(filters?: { folderId?: string; archived?: boolean }) { - const { folderId, archived } = filters || {}; + async listFiles(filters?: { + folderId?: string; + archived?: boolean; + /** + * Scope the listing to a project. Requires the token owner to have + * `STORAGE:READ` role in that project; otherwise the backend returns + * 403. Per-entity permission overrides are applied server-side. + */ + projectId?: string; + }) { + const { folderId, archived, projectId } = filters || {}; if (folderId) { return await this.#requestApi( 'GET', @@ -385,7 +429,11 @@ export class EngineServicesClient { ); } return await this.#requestApi('GET', `${ITEM_PATH}`, { - query: { itemType: ITEM_TYPE_FILE, archived }, + query: { + itemType: ITEM_TYPE_FILE, + archived, + ...(projectId && { projectId }), + }, }); } @@ -478,10 +526,22 @@ export class EngineServicesClient { * @param params - Optional filters for parent folder and archive status. * @returns Array of folder items. */ - async listFolders(params?: { parentFolderId?: string; archived?: boolean }) { - const { archived, parentFolderId } = params || {}; + async listFolders(params?: { + parentFolderId?: string; + archived?: boolean; + /** + * Scope the listing to a project. Requires the token owner to have + * `STORAGE:READ` in that project; returns 403 otherwise. + */ + projectId?: string; + }) { + const { archived, parentFolderId, projectId } = params || {}; return await this.#requestApi('GET', FOLDER_PATH, { - query: { parentFolderId, archived }, + query: { + parentFolderId, + archived, + ...(projectId && { projectId }), + }, }); } @@ -569,8 +629,8 @@ export class EngineServicesClient { * @param params - Optional filters for folder and version inclusion. * @returns Array of component items. */ - async listComponents(params?: GetItemsParams) { - const { folderId, ShowVersions } = params || {}; + async listComponents(params?: GetItemsParams & { projectId?: string }) { + const { folderId, ShowVersions, projectId } = params || {}; if (folderId) { return await this.#requestApi( 'GET', @@ -587,6 +647,7 @@ export class EngineServicesClient { query: { itemType: ITEM_TYPE_COMPONENT, ...(ShowVersions && { ShowVersions }), + ...(projectId && { projectId }), }, }); } @@ -831,8 +892,8 @@ export class EngineServicesClient { * @param params - Optional filters for folder and version inclusion. * @returns Array of app items. */ - async listApps(params?: GetItemsParams) { - const { folderId, ShowVersions } = params || {}; + async listApps(params?: GetItemsParams & { projectId?: string }) { + const { folderId, ShowVersions, projectId } = params || {}; if (folderId) { return await this.#requestApi( 'GET', @@ -849,6 +910,7 @@ export class EngineServicesClient { query: { itemType: ITEM_TYPE_APP, ...(ShowVersions && { ShowVersions }), + ...(projectId && { projectId }), }, }); } @@ -909,14 +971,21 @@ export class EngineServicesClient { /** * Triggers server-side execution of a cloud component. + * + * Pass `projectId` in `executionParams` when running the component in the + * context of a specific project. The backend validates that the component + * is linked to that project AND that the user has execute permission + * there; a foreign `projectId` is rejected with 403. Omit `projectId` for + * personal executions (ownership path). + * * @param componentId - The component's unique identifier. - * @param executionParams - Arbitrary parameters passed to the component's `main()` function. + * @param executionParams - Arbitrary parameters passed to the component's `main()` function. Include `projectId` to scope the execution. * @param versionTag - Optional version to execute (defaults to latest). * @returns An object containing the `executionId` to track progress. */ async executeComponent( componentId: string, - executionParams: object, + executionParams: { projectId?: string; [key: string]: unknown }, versionTag?: string, ) { if (this.localServerUrl) { @@ -965,22 +1034,34 @@ export class EngineServicesClient { /** * Lists all executions for a given component. + * + * When `projectId` is supplied, the backend scopes the query to that + * project — returning only executions launched in that context AND + * enforcing that the caller has access to the component there. Without + * `projectId`, the caller's personal executions for the component are + * returned. + * * @param componentId - The component's unique identifier. + * @param projectId - Optional project scope. * @returns Array of execution entities. */ - async listExecutions(componentId: string) { + async listExecutions(componentId: string, projectId?: string) { if (this.localServerUrl) { - const url = `${this.localServerUrl}/api/${PROCESS_PATH}/${componentId}/progress`; + const qs = projectId ? `?projectId=${encodeURIComponent(projectId)}` : ''; + const url = `${this.localServerUrl}/api/${PROCESS_PATH}/${componentId}/progress${qs}`; const response = await fetch(url); if (!response.ok) { const text = await response.text().catch(() => ''); - throw new Error(`Local server request failed: ${response.status} - ${text}`); + throw new Error( + `Local server request failed: ${response.status} - ${text}`, + ); } return (await response.json()) as ExecutionEntity[]; } return await this.#requestApi( 'GET', `${PROCESS_PATH}/${componentId}/progress`, + { query: { ...(projectId && { projectId }) } }, ); } @@ -1199,52 +1280,17 @@ export class EngineServicesClient { ); } - // ─── Projects ──────────────────────────────────────────────────── - - /** - * Gets a project by ID. Requires JWT auth or a future PublicAuth endpoint. - * @param projectId - The project's unique identifier. - * @returns The project entity. - */ - async getProject(projectId: string) { - return await this.#requestApi( - 'GET', - `${PROJECT_PATH}/${projectId}`, - ); - } - - /** - * Gets the full project data, including users, roles, files, and folders. - * User data is stripped of sensitive fields server-side. - * @param projectId - The project's unique identifier. - * @returns The aggregated project data DTO. - */ - async getProjectData(projectId: string) { - return await this.#requestApi( - 'GET', - `${PROJECT_PATH}/${projectId}/data`, - ); - } - - // ─── Permissions ───────────────────────────────────────────────── - - /** - * Checks whether the current token has a specific permission within a project. - * @param params - Resource ID, resource type, action, and project ID. - * @returns An object with `hasPermission: boolean`. - */ - async checkPermission(params: { - resourceId: string; - resourceType: string; - action: string; - projectId: string; - }) { - return await this.#requestApi<{ hasPermission: boolean }>( - 'GET', - `${PROJECT_PATH}/permissions/check`, - { query: params }, - ); - } + // Project-scoped listings happen via the main list methods — e.g. + // `listFiles({ projectId })`, `listFolders({ projectId })`, + // `listApps({ projectId })`, `listComponents({ projectId })`. Those call + // `GET /item?projectId=...` / `/item/folder?projectId=...`, which accept + // both API tokens and JWT and apply per-entity permission filtering on + // the server. + // + // Methods that hit the JWT-only `/project/:id/*` and + // `/project/permissions/check*` routes (getProject, getProjectData, + // checkPermission, checkPermissionBatch) live on `PlatformClient` — they + // cannot be called with an access token. // ─── Private Helpers ───────────────────────────────────────────── diff --git a/src/core/platform-client.test.ts b/src/core/platform-client.test.ts new file mode 100644 index 0000000..6b25f6c --- /dev/null +++ b/src/core/platform-client.test.ts @@ -0,0 +1,241 @@ +import { + describe, + it, + expect, + beforeEach, + afterEach, + vi, + type Mock, +} from 'vitest'; +import { EngineServicesClient } from './client'; +import { PlatformClient } from './platform-client'; + +const API = 'https://api.example.com'; +const JWT = 'test-jwt'; + +function okResponse(data: unknown): Response { + return { + ok: true, + status: 200, + statusText: 'OK', + text: async () => JSON.stringify(data), + json: async () => data, + } as unknown as Response; +} + +function parseUrl(url: string): { pathname: string; params: URLSearchParams } { + const u = new URL(url); + return { pathname: u.pathname, params: u.searchParams }; +} + +describe('PlatformClient — bearer-configured EngineServicesClient', () => { + let fetchMock: Mock; + + beforeEach(() => { + fetchMock = vi.fn(); + globalThis.fetch = fetchMock as unknown as typeof fetch; + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('extends EngineServicesClient — full method surface is inherited', () => { + const client = new PlatformClient(JWT, API); + expect(client).toBeInstanceOf(EngineServicesClient); + // Methods that only exist on the parent must be callable on PlatformClient. + expect(typeof client.executeComponent).toBe('function'); + expect(typeof client.onExecutionProgress).toBe('function'); + expect(typeof client.listFiles).toBe('function'); + expect(typeof client.checkPermission).toBe('function'); + }); + + describe('token provider — refresh-friendly auth', () => { + it('string token is sent verbatim', async () => { + fetchMock.mockResolvedValue(okResponse([])); + const client = new PlatformClient('static-jwt', API); + await client.listFiles(); + const init = fetchMock.mock.calls[0][1] as RequestInit; + expect((init.headers as Record).Authorization).toBe( + `Bearer static-jwt`, + ); + }); + + it('provider function is called on every request', async () => { + fetchMock.mockResolvedValue(okResponse([])); + const provider = vi.fn(); + provider + .mockResolvedValueOnce('token-v1') + .mockResolvedValueOnce('token-v2') + .mockResolvedValueOnce('token-v3'); + + const client = new PlatformClient(provider, API); + await client.listFiles(); + await client.listFolders(); + await client.getProject('proj-1'); + + expect(provider).toHaveBeenCalledTimes(3); + const authHeaders = fetchMock.mock.calls.map( + (call) => + (call[1] as RequestInit).headers as Record, + ); + expect(authHeaders[0].Authorization).toBe('Bearer token-v1'); + expect(authHeaders[1].Authorization).toBe('Bearer token-v2'); + expect(authHeaders[2].Authorization).toBe('Bearer token-v3'); + }); + + it('synchronous provider also works', async () => { + fetchMock.mockResolvedValue(okResponse([])); + let current = 'one'; + const client = new PlatformClient(() => current, API); + await client.listFiles(); + current = 'two'; + await client.listFolders(); + const authHeaders = fetchMock.mock.calls.map( + (c) => (c[1] as RequestInit).headers as Record, + ); + expect(authHeaders[0].Authorization).toBe('Bearer one'); + expect(authHeaders[1].Authorization).toBe('Bearer two'); + }); + }); + + describe('fromPlatformContext', () => { + afterEach(() => { + (globalThis as unknown as { window?: Window }).window = undefined; + }); + + it('creates a PlatformClient from window context', () => { + (globalThis as unknown as { window: object }).window = { + __THATOPEN_CONTEXT__: { + appId: 'app-1', + projectId: 'proj-1', + accessToken: 'ctx-jwt', + apiUrl: 'https://api.example.com', + }, + }; + const client = PlatformClient.fromPlatformContext(); + expect(client).toBeInstanceOf(PlatformClient); + expect(client.context.projectId).toBe('proj-1'); + expect(client.context.accessToken).toBe('ctx-jwt'); + }); + }); + + it('always authenticates with Bearer regardless of props', async () => { + fetchMock.mockResolvedValue(okResponse([])); + const client = new PlatformClient(JWT, API, { retries: 2 }); + await client.listFiles(); + const call = fetchMock.mock.calls[0]; + const url = call[0] as string; + const init = call[1] as RequestInit; + const { params } = parseUrl(url); + expect(params.get('accessToken')).toBeNull(); + expect((init.headers as Record).Authorization).toBe( + `Bearer ${JWT}`, + ); + }); + + it('forwards projectId on project-scoped listings', async () => { + fetchMock.mockResolvedValue(okResponse([])); + const client = new PlatformClient(JWT, API); + await client.listFiles({ projectId: 'proj-1' }); + const { pathname, params } = parseUrl(fetchMock.mock.calls[0][0] as string); + expect(pathname).toBe('/api/item'); + expect(params.get('itemType')).toBe('FILE'); + expect(params.get('projectId')).toBe('proj-1'); + }); + + describe('project routes (JWT-only; lives on PlatformClient only)', () => { + it('getProject GETs /project/:id', async () => { + fetchMock.mockResolvedValue(okResponse({ _id: 'proj-1' })); + const client = new PlatformClient(JWT, API); + const result = await client.getProject('proj-1'); + expect(result).toEqual({ _id: 'proj-1' }); + const { pathname } = parseUrl(fetchMock.mock.calls[0][0] as string); + expect(pathname).toBe('/api/project/proj-1'); + }); + + it('getProjectData GETs /project/:id/data', async () => { + fetchMock.mockResolvedValue(okResponse({ project: { _id: 'proj-1' } })); + const client = new PlatformClient(JWT, API); + await client.getProjectData('proj-1'); + const { pathname } = parseUrl(fetchMock.mock.calls[0][0] as string); + expect(pathname).toBe('/api/project/proj-1/data'); + }); + + it('checkPermission returns { hasPermission, scope }', async () => { + fetchMock.mockResolvedValue( + okResponse({ hasPermission: true, scope: 'project' }), + ); + const client = new PlatformClient(JWT, API); + const result = await client.checkPermission({ + resourceType: 'APP', + action: 'READ', + projectId: 'proj-1', + }); + expect(result).toEqual({ hasPermission: true, scope: 'project' }); + const { pathname, params } = parseUrl( + fetchMock.mock.calls[0][0] as string, + ); + expect(pathname).toBe('/api/project/permissions/check'); + expect(params.get('resourceType')).toBe('APP'); + expect(params.get('action')).toBe('READ'); + expect(params.get('projectId')).toBe('proj-1'); + }); + + it('checkPermissionBatch POSTs /permissions/check/batch and returns results', async () => { + fetchMock.mockResolvedValue( + okResponse({ + results: [ + { hasPermission: true, scope: 'project' }, + { hasPermission: false, scope: 'none' }, + ], + }), + ); + const client = new PlatformClient(JWT, API); + const checks = [ + { resourceType: 'APP', action: 'READ', projectId: 'proj-1' }, + { resourceType: 'APP', action: 'DELETE', projectId: 'proj-1' }, + ]; + const results = await client.checkPermissionBatch(checks); + expect(results).toHaveLength(2); + const call = fetchMock.mock.calls[0]; + const init = call[1] as RequestInit; + const { pathname } = parseUrl(call[0] as string); + expect(pathname).toBe('/api/project/permissions/check/batch'); + expect(init.method).toBe('POST'); + expect(init.body).toBe(JSON.stringify({ checks })); + }); + + it('EngineServicesClient does not expose project/permission methods', () => { + // These methods hit JWT-only backend routes; they must not exist on + // the API-token-capable parent class. + const parent = {} as EngineServicesClient as unknown as Record< + string, + unknown + >; + const proto = Object.getPrototypeOf( + new (class extends EngineServicesClient {})(JWT, API), + ); + // Walk prototype chain to collect own method names on EngineServicesClient. + const names = Object.getOwnPropertyNames( + Object.getPrototypeOf(proto) as object, + ); + expect(names).not.toContain('getProject'); + expect(names).not.toContain('getProjectData'); + expect(names).not.toContain('checkPermission'); + expect(names).not.toContain('checkPermissionBatch'); + void parent; + }); + }); + + it('TypeScript: constructor has no API-token / useBearer escape hatches', () => { + // The constructor only takes (bearerToken, apiUrl, props?). `useBearer` + // is omitted from the props type — callers can't turn bearer off. + // This test documents the contract; if the type widens, it breaks. + type Ctor = ConstructorParameters; + type Props = Ctor[2]; + // @ts-expect-error — useBearer is not assignable on PlatformClient props. + const _badProps: Props = { useBearer: false }; + void _badProps; + }); +}); diff --git a/src/core/platform-client.ts b/src/core/platform-client.ts new file mode 100644 index 0000000..d4a620c --- /dev/null +++ b/src/core/platform-client.ts @@ -0,0 +1,176 @@ +import { + EngineServicesClient, + EngineServicesClientProps, +} from './client'; +import { Project, ProjectData } from '../types/projects'; +import { ThatOpenContext } from '../types/context'; + +const PROJECT_PATH = 'project'; + +/** Scope by which a permission was granted (or `'none'` if denied). */ +export type PermissionScope = 'global' | 'project' | 'entity' | 'none'; + +/** Result of a single permission check. */ +export interface PermissionCheckResult { + hasPermission: boolean; + scope: PermissionScope; +} + +/** One entry in a batch permission check. */ +export interface PermissionCheckEntry { + resourceType: string; + action: string; + resourceId?: string; + projectId?: string; +} + +/** + * Accepts either a static JWT string or a provider function that returns the + * current JWT. Use a provider to keep refresh in the caller's hands — the + * client calls it on every request so expired tokens never stick. + * + * @example Static token (simplest): + * ```ts + * new PlatformClient(jwt, apiUrl) + * ``` + * + * @example Auth0 React: + * ```ts + * const { getAccessTokenSilently } = useAuth0(); + * new PlatformClient(() => getAccessTokenSilently(), apiUrl) + * ``` + */ +export type BearerTokenSource = + | string + | (() => string | Promise); + +/** + * Client for apps, frontends, and any caller authenticating with a user JWT. + * Extends `EngineServicesClient` — the full API-token-compatible surface is + * inherited. On top, it exposes the JWT-only routes `getProject`, + * `getProjectData`, `checkPermission`, and `checkPermissionBatch`. Those hit + * `ProjectController` which is guarded by `AccountActiveGuard + + * ProjectAccessGuard` and is not reachable via an access token. + * + * **Token refresh.** The constructor accepts a function that returns a JWT + * (sync or async); the client calls it before every request, so an Auth0 + * SDK's `getAccessTokenSilently()` or similar refreshing source Just Works. + * + * Use `EngineServicesClient` for components (API-token auth, local server, + * WebSocket progress). Use `PlatformClient` when you have a user JWT and + * need project-level reads or permission introspection. + * + * @example + * ```ts + * const client = new PlatformClient( + * () => auth0.getAccessTokenSilently(), + * 'https://api.thatopen.com', + * ); + * const project = await client.getProject(projectId); + * ``` + */ +export class PlatformClient extends EngineServicesClient { + readonly #tokenProvider?: () => string | Promise; + + /** + * @param token - A bearer JWT, OR a function returning the current JWT + * (sync or async). When a function is passed, it's invoked before every + * request — ideal for token-refreshing sources like Auth0. + * @param apiUrl - Base URL of the API (e.g. `https://api.thatopen.com`). + * @param props - Optional client configuration. `useBearer` is forced to + * `true` and cannot be overridden. + */ + constructor( + token: BearerTokenSource, + apiUrl: string, + props?: Omit, + ) { + // Seed the parent with a string (possibly empty when a provider is + // supplied); the override below routes each request through the + // provider when present. + const initialToken = typeof token === 'string' ? token : ''; + super(initialToken, apiUrl, { ...props, useBearer: true }); + if (typeof token === 'function') { + this.#tokenProvider = token; + } + } + + protected async resolveAccessToken(): Promise { + if (this.#tokenProvider) return await this.#tokenProvider(); + return super.resolveAccessToken(); + } + + /** + * Creates a client from the platform context injected into + * `window.__THATOPEN_CONTEXT__` by the That Open Platform. Recommended + * entry point for apps running inside the platform's iframe — the context + * carries a fresh JWT on every navigation. + */ + static fromPlatformContext( + props?: Omit, + ): PlatformClient { + const ctx: ThatOpenContext = + (typeof window !== 'undefined' + ? window.__THATOPEN_CONTEXT__ + : null) || { appId: '', projectId: '', accessToken: '', apiUrl: '' }; + const client = new PlatformClient(ctx.accessToken, ctx.apiUrl, props); + (client as { context: ThatOpenContext }).context = ctx; + return client; + } + + // ─── Projects (JWT-only backend routes) ────────────────────────── + + /** + * Gets a project by ID. JWT-only — lives here because + * `GET /project/:id` is guarded by `AccountActiveGuard + ProjectAccessGuard`. + */ + async getProject(projectId: string) { + return await this.request('GET', `${PROJECT_PATH}/${projectId}`); + } + + /** + * Gets the full project data (users, roles, files, folders) for a project. + * User data is stripped of sensitive fields server-side. + */ + async getProjectData(projectId: string) { + return await this.request( + 'GET', + `${PROJECT_PATH}/${projectId}/data`, + ); + } + + // ─── Permissions (JWT-only backend routes) ─────────────────────── + + /** + * Checks whether the caller has a specific permission within a project. + * Returns `{ hasPermission, scope }` where `scope` is `'global'` for + * admin/owner, `'project'` for a role broad grant, `'entity'` for a + * per-entity override, `'none'` for denied. + */ + async checkPermission(params: { + resourceId?: string; + resourceType: string; + action: string; + projectId?: string; + }) { + return await this.request( + 'GET', + `${PROJECT_PATH}/permissions/check`, + { query: params as Record }, + ); + } + + /** + * Batch variant of {@link checkPermission}. Evaluates multiple checks in a + * single round-trip; results come back in the same order as `checks`. + */ + async checkPermissionBatch(checks: PermissionCheckEntry[]) { + const response = await this.request<{ + results: PermissionCheckResult[]; + }>('POST', `${PROJECT_PATH}/permissions/check/batch`, { + body: JSON.stringify({ checks }), + contentType: 'application/json', + }); + return response.results; + } +} diff --git a/src/index.ts b/src/index.ts index 1d6b4b0..8cf8eac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export * from './core/client'; +export * from './core/platform-client'; export * from './types/items'; export * from './types/base'; export * from './types/execution'; diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..04df298 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['src/**/*.test.ts'], + environment: 'node', + globals: false, + }, +}); diff --git a/yarn.lock b/yarn.lock index a4d0407..e938324 100644 --- a/yarn.lock +++ b/yarn.lock @@ -233,6 +233,28 @@ resolved "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz" integrity sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow== +"@emnapi/core@1.9.2": + version "1.9.2" + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.9.2.tgz#3870265ecffc7352d01ead62d8d83d8358a2d034" + integrity sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA== + dependencies: + "@emnapi/wasi-threads" "1.2.1" + tslib "^2.4.0" + +"@emnapi/runtime@1.9.2": + version "1.9.2" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.9.2.tgz#8b469a3db160817cadb1de9050211a9d1ea84fa2" + integrity sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw== + dependencies: + tslib "^2.4.0" + +"@emnapi/wasi-threads@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz#28fed21a1ba1ce797c44a070abc94d42f3ae8548" + integrity sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w== + dependencies: + tslib "^2.4.0" + "@esbuild/aix-ppc64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" @@ -689,6 +711,13 @@ resolved "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz" integrity sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug== +"@napi-rs/wasm-runtime@^1.1.3": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz#a46bbfedc29751b7170c5d23bc1d8ee8c7e3c1e1" + integrity sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow== + dependencies: + "@tybys/wasm-util" "^0.10.1" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" @@ -710,11 +739,100 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@oxc-project/types@=0.124.0": + version "0.124.0" + resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.124.0.tgz#1dfd7b3fbb98febc2f91b505f48c940db73c8701" + integrity sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg== + "@pkgr/core@^0.2.9": version "0.2.9" resolved "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz" integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA== +"@rolldown/binding-android-arm64@1.0.0-rc.15": + version "1.0.0-rc.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz#ca20574c469ade7b941f90c9af5e83e7c67f06b7" + integrity sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA== + +"@rolldown/binding-darwin-arm64@1.0.0-rc.15": + version "1.0.0-rc.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz#ce2c5c7fc4958dfc94783dc09b3d09f3c2e1d072" + integrity sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg== + +"@rolldown/binding-darwin-x64@1.0.0-rc.15": + version "1.0.0-rc.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz#251ecdf1fdb751031cb6486907c105daaf9dab21" + integrity sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw== + +"@rolldown/binding-freebsd-x64@1.0.0-rc.15": + version "1.0.0-rc.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz#dbcfe95f409bf671a77bd83bff0fdc877d217728" + integrity sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw== + +"@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15": + version "1.0.0-rc.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz#ea002b45445be6f9ed1883a834b335bc2ccd510f" + integrity sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA== + +"@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15": + version "1.0.0-rc.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz#12b96e7e7821a9dc2cd5c670ad56882987ed5c62" + integrity sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w== + +"@rolldown/binding-linux-arm64-musl@1.0.0-rc.15": + version "1.0.0-rc.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz#738b0f62f0b65bf676dfe48595017f1883859d1f" + integrity sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ== + +"@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15": + version "1.0.0-rc.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz#3088b9fbc2783033985b558316f87f39281bc533" + integrity sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ== + +"@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15": + version "1.0.0-rc.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz#ac0aa6f1b72e3151d56c43145a71c745cf862a9a" + integrity sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ== + +"@rolldown/binding-linux-x64-gnu@1.0.0-rc.15": + version "1.0.0-rc.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz#b8cf27aa5be6da641c22dad5665d0240551d2dec" + integrity sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA== + +"@rolldown/binding-linux-x64-musl@1.0.0-rc.15": + version "1.0.0-rc.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz#4531f9eca77963935026634ba9b61c2535340534" + integrity sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw== + +"@rolldown/binding-openharmony-arm64@1.0.0-rc.15": + version "1.0.0-rc.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz#66ff691a65f9325171bced98e353b4cc4b0095c3" + integrity sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg== + +"@rolldown/binding-wasm32-wasi@1.0.0-rc.15": + version "1.0.0-rc.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz#7db6c90aa510eef65d7d0f14e8ca23775e8e5eee" + integrity sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q== + dependencies: + "@emnapi/core" "1.9.2" + "@emnapi/runtime" "1.9.2" + "@napi-rs/wasm-runtime" "^1.1.3" + +"@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15": + version "1.0.0-rc.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz#81f9097abbd4493cc13373b26f5a3da8461dbb47" + integrity sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA== + +"@rolldown/binding-win32-x64-msvc@1.0.0-rc.15": + version "1.0.0-rc.15" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz#cef11bc89149f3a77771727be75490fbb13ae193" + integrity sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g== + +"@rolldown/pluginutils@1.0.0-rc.15": + version "1.0.0-rc.15" + resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz#e75d7731593e195d23710f9ff49bf5c745c96682" + integrity sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g== + "@rollup/pluginutils@^4.2.1": version "4.2.1" resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz" @@ -900,36 +1018,50 @@ resolved "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz" integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== -"@thatopen/components-front@^3.3.1": - version "3.3.1" - resolved "https://registry.npmjs.org/@thatopen/components-front/-/components-front-3.3.1.tgz" - integrity sha512-Y5OhrtOLs6dLhT+ELGcxNDY0k0D/F4Rjv8+9FYF9SjNiB4k1ke3aC6Uy8gsx/tZtFmQ8Ad+bLrEtQYOGr9AZgw== +"@standard-schema/spec@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" + integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== + +"@thatopen/components-front@~3.4.0": + version "3.4.2" + resolved "https://registry.yarnpkg.com/@thatopen/components-front/-/components-front-3.4.2.tgz#16cbac5e6432b35c99489b58933cf0466568e71a" + integrity sha512-Mac9dbxFE5fW4KIp9hPfS30+v2ySFtOHUjpl5VORTvgnlv2x9VVEdYsFV/K3xVOTXoxvsCtnU8sGbyTH8noYUA== dependencies: - "@thatopen/components" "~3.3.0" + "@thatopen/components" "~3.4.0" earcut "^3.0.1" -"@thatopen/components@^3.3.1", "@thatopen/components@~3.3.0": - version "3.3.1" - resolved "https://registry.npmjs.org/@thatopen/components/-/components-3.3.1.tgz" - integrity sha512-DkR7gHrjElFKHHZtgatF8f6Ry1fbbXMySsazaQ7zK0JTvExyPbcw5pQs03HOW0VjjJLf2IEOMycHTurvzOJesQ== +"@thatopen/components@~3.4.0": + version "3.4.2" + resolved "https://registry.yarnpkg.com/@thatopen/components/-/components-3.4.2.tgz#b7dde8f4888f9856261b22df2e560b29bd478b7e" + integrity sha512-c+PLlXRoC0gAhqeYLelLAc0dTEHsiv9DSMlBh1THsxC1Y8Pro+E/RlKh3KdgtcZpbThVBJm9JDzgqEt7oXJRbA== dependencies: - camera-controls "^2.9.0" - fast-xml-parser "4.4.1" + fast-xml-parser "5.3.7" jszip "3.10.1" - three-mesh-bvh "0.7.0" + three-mesh-bvh "0.9.9" -"@thatopen/ui-obc@^3.3.3": - version "3.3.3" - resolved "https://registry.npmjs.org/@thatopen/ui-obc/-/ui-obc-3.3.3.tgz" - integrity sha512-P/Yo+5mVR8VReoi2MoxuLKaQNekDppA0ro5R5mnqn9PKoHKQhukZtfx4YUcyywMxvkmOOd+PiqUpOP1aqtTffg== +"@thatopen/fragments@~3.4.0": + version "3.4.3" + resolved "https://registry.yarnpkg.com/@thatopen/fragments/-/fragments-3.4.3.tgz#5b4c0e8e02640cb28255715176e65a2fdc1590a2" + integrity sha512-GKquJNEu5mBQcxrUDE0uGVPpysb6+6ZYIIhTKvYhlciwcKWZMRCYkeMI6OgW8194WQwiStBAGs/bjshj23ivLg== dependencies: - "@thatopen/ui" "~3.3.0" + earcut "^3.0.1" + flatbuffers "25.2.10" + lru-cache "11.1.0" + pako "2.1.0" + +"@thatopen/ui-obc@~3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@thatopen/ui-obc/-/ui-obc-3.4.0.tgz#62e5e861c09ac7a1a95db792c9a4d7eb72ac2359" + integrity sha512-sleuwPDvZs7CABjJqDnth5nX3rj7zv5MjUEHMzfSydmIXGuWnTTsq+z4n2aLuCSiX70rJ5PUgTBj6Q2J3I9QfA== + dependencies: + "@thatopen/ui" "~3.4.0" lit "3.3.1" -"@thatopen/ui@^3.3.3", "@thatopen/ui@~3.3.0": - version "3.3.3" - resolved "https://registry.npmjs.org/@thatopen/ui/-/ui-3.3.3.tgz" - integrity sha512-6mpHiRDl796tpHaPnQItHp0YqSjBENMogmy6De4NLEQ/Q+XzOPtAfIVuz5lnNmyigj5b3MfPn9+VW/le8gGxqg== +"@thatopen/ui@~3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@thatopen/ui/-/ui-3.4.0.tgz#9f66d06cf720af2d7cd1fa20b36359920120be80" + integrity sha512-m9t4SzUD0sRms8ktqLKTWreKj2eX3VtJ9mf6ED3b3ePDjN28VQl3qYwBzU6wD1r6J5I9dhcEbtJhfsTMvXEzTQ== dependencies: "@floating-ui/dom" "1.6.3" chart.js "^4.5.0" @@ -942,11 +1074,26 @@ resolved "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz" integrity sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA== +"@tybys/wasm-util@^0.10.1": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz#ecddd3205cf1e2d5274649ff0eedd2991ed7f414" + integrity sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg== + dependencies: + tslib "^2.4.0" + "@types/argparse@1.0.38": version "1.0.38" resolved "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz" integrity sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA== +"@types/chai@^5.2.2": + version "5.2.3" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-5.2.3.tgz#8e9cd9e1c3581fa6b341a5aed5588eb285be0b4a" + integrity sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA== + dependencies: + "@types/deep-eql" "*" + assertion-error "^2.0.1" + "@types/cors@^2.8.12": version "2.8.19" resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz" @@ -954,6 +1101,11 @@ dependencies: "@types/node" "*" +"@types/deep-eql@*": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/deep-eql/-/deep-eql-4.0.2.tgz#334311971d3a07121e7eb91b684a605e7eea9cbd" + integrity sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw== + "@types/eslint@^8.4.5": version "8.56.12" resolved "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz" @@ -1093,6 +1245,66 @@ "@typescript-eslint/types" "7.18.0" eslint-visitor-keys "^3.4.3" +"@vitest/expect@4.1.4": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-4.1.4.tgz#1507e51c53969723c99e8a7f054aa12cfa7c1a4d" + integrity sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww== + dependencies: + "@standard-schema/spec" "^1.1.0" + "@types/chai" "^5.2.2" + "@vitest/spy" "4.1.4" + "@vitest/utils" "4.1.4" + chai "^6.2.2" + tinyrainbow "^3.1.0" + +"@vitest/mocker@4.1.4": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-4.1.4.tgz#5d22e99d8dbacf2f77f7a4c30a6e17eece7f25ef" + integrity sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg== + dependencies: + "@vitest/spy" "4.1.4" + estree-walker "^3.0.3" + magic-string "^0.30.21" + +"@vitest/pretty-format@4.1.4": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-4.1.4.tgz#0ee79cd2ef8321330dabb8cc57ba9bce237e7183" + integrity sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A== + dependencies: + tinyrainbow "^3.1.0" + +"@vitest/runner@4.1.4": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-4.1.4.tgz#8f884f265efabfdd8a5ee393cfe622a01ec849c2" + integrity sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ== + dependencies: + "@vitest/utils" "4.1.4" + pathe "^2.0.3" + +"@vitest/snapshot@4.1.4": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-4.1.4.tgz#600c04ee1c598d4e6ce219afae684ff21c3e187d" + integrity sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw== + dependencies: + "@vitest/pretty-format" "4.1.4" + "@vitest/utils" "4.1.4" + magic-string "^0.30.21" + pathe "^2.0.3" + +"@vitest/spy@4.1.4": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-4.1.4.tgz#b955fcef98bcc746e7fc61d17d4725b43b33fa6d" + integrity sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ== + +"@vitest/utils@4.1.4": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-4.1.4.tgz#9518fb0ad0903ae455e82e063fa18e7558aa6065" + integrity sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw== + dependencies: + "@vitest/pretty-format" "4.1.4" + convert-source-map "^2.0.0" + tinyrainbow "^3.1.0" + "@volar/language-core@1.11.1", "@volar/language-core@~1.11.1": version "1.11.1" resolved "https://registry.npmjs.org/@volar/language-core/-/language-core-1.11.1.tgz" @@ -1221,6 +1433,11 @@ array-union@^2.1.0: resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" @@ -1265,10 +1482,10 @@ callsites@^3.0.0: resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camera-controls@^2.9.0: - version "2.10.1" - resolved "https://registry.npmjs.org/camera-controls/-/camera-controls-2.10.1.tgz" - integrity sha512-KnaKdcvkBJ1Irbrzl8XD6WtZltkRjp869Jx8c0ujs9K+9WD+1D7ryBsCiVqJYUqt6i/HR5FxT7RLASieUD+Q5w== +chai@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/chai/-/chai-6.2.2.tgz#ae41b52c9aca87734505362717f3255facda360e" + integrity sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg== chalk@^4.0.0: version "4.1.2" @@ -1332,6 +1549,11 @@ concat-map@0.0.1: resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + cookie@~0.7.2: version "0.7.2" resolved "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz" @@ -1381,6 +1603,11 @@ detect-indent@^6.0.0: resolved "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz" integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== +detect-libc@^2.0.3: + version "2.1.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" + integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" @@ -1442,6 +1669,11 @@ entities@^7.0.1: resolved "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz" integrity sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA== +es-module-lexer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-2.0.0.tgz#f657cd7a9448dcdda9c070a3cb75e5dc1e85f5b1" + integrity sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw== + esbuild@^0.21.3: version "0.21.5" resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz" @@ -1617,11 +1849,23 @@ estree-walker@^2.0.1, estree-walker@^2.0.2: resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz" integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + esutils@^2.0.2: version "2.0.3" resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +expect-type@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.3.0.tgz#0d58ed361877a31bbc4dd6cf71bbfef7faf6bd68" + integrity sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA== + extendable-error@^0.1.5: version "0.1.7" resolved "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz" @@ -1658,12 +1902,12 @@ fast-levenshtein@^2.0.6: resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fast-xml-parser@4.4.1: - version "4.4.1" - resolved "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz" - integrity sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw== +fast-xml-parser@5.3.7: + version "5.3.7" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-5.3.7.tgz#81694e71ff0e568cbb6befade342f2a7e58aa1d9" + integrity sha512-JzVLro9NQv92pOM/jTCR6mHlJh2FGwtomH8ZQjhFj/R29P2Fnj38OgPJVtcvYw6SuKClhgYuwUZf5b3rd8u2mA== dependencies: - strnum "^1.0.5" + strnum "^2.1.2" fastq@^1.6.0: version "1.20.1" @@ -1672,6 +1916,11 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + fflate@~0.8.2: version "0.8.2" resolved "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz" @@ -1715,6 +1964,11 @@ flat-cache@^4.0.0: flatted "^3.2.9" keyv "^4.5.4" +flatbuffers@25.2.10: + version "25.2.10" + resolved "https://registry.yarnpkg.com/flatbuffers/-/flatbuffers-25.2.10.tgz#308b750545f62db670ca4c9d7dbc66161420a95e" + integrity sha512-7JlN9ZvLDG1McO3kbX0k4v+SUAg48L1rIwEvN6ZQl/eCtgJz9UylTMzE9wrmYrcorgxm3CX/3T/w5VAub99UUw== + flatted@^3.2.9: version "3.3.3" resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz" @@ -1995,6 +2249,80 @@ lie@~3.3.0: dependencies: immediate "~3.0.5" +lightningcss-android-arm64@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz#f033885116dfefd9c6f54787523e3514b61e1968" + integrity sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg== + +lightningcss-darwin-arm64@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz#50b71871b01c8199584b649e292547faea7af9b5" + integrity sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ== + +lightningcss-darwin-x64@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz#35f3e97332d130b9ca181e11b568ded6aebc6d5e" + integrity sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w== + +lightningcss-freebsd-x64@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz#9777a76472b64ed6ff94342ad64c7bafd794a575" + integrity sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig== + +lightningcss-linux-arm-gnueabihf@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz#13ae652e1ab73b9135d7b7da172f666c410ad53d" + integrity sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw== + +lightningcss-linux-arm64-gnu@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz#417858795a94592f680123a1b1f9da8a0e1ef335" + integrity sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ== + +lightningcss-linux-arm64-musl@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz#6be36692e810b718040802fd809623cffe732133" + integrity sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg== + +lightningcss-linux-x64-gnu@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz#0b7803af4eb21cfd38dd39fe2abbb53c7dd091f6" + integrity sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA== + +lightningcss-linux-x64-musl@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz#88dc8ba865ddddb1ac5ef04b0f161804418c163b" + integrity sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg== + +lightningcss-win32-arm64-msvc@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz#4f30ba3fa5e925f5b79f945e8cc0d176c3b1ab38" + integrity sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw== + +lightningcss-win32-x64-msvc@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz#141aa5605645064928902bb4af045fa7d9f4220a" + integrity sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q== + +lightningcss@^1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.32.0.tgz#b85aae96486dcb1bf49a7c8571221273f4f1e4a9" + integrity sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ== + dependencies: + detect-libc "^2.0.3" + 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" + lit-element@^4.2.0: version "4.2.2" resolved "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz" @@ -2059,6 +2387,11 @@ lodash@~4.17.15: resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz" integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== +lru-cache@11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.1.0.tgz#afafb060607108132dbc1cf8ae661afb69486117" + integrity sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A== + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" @@ -2066,7 +2399,7 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -magic-string@^0.30.8: +magic-string@^0.30.21, magic-string@^0.30.8: version "0.30.21" resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz" integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== @@ -2159,6 +2492,11 @@ object-assign@^4: resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== +obug@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/obug/-/obug-2.1.1.tgz#2cba74ff241beb77d63055ddf4cd1e9f90b538be" + integrity sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ== + optionator@^0.9.3: version "0.9.4" resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz" @@ -2228,6 +2566,11 @@ package-manager-detector@^0.2.0: dependencies: quansync "^0.2.7" +pako@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" + integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== + pako@~1.0.2: version "1.0.11" resolved "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz" @@ -2265,6 +2608,11 @@ path-type@^4.0.0: resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pathe@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" + integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== + picocolors@^1.1.0, picocolors@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" @@ -2280,6 +2628,11 @@ picomatch@^4.0.2: resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz" integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== +picomatch@^4.0.3, picomatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589" + integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== + pify@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz" @@ -2294,6 +2647,15 @@ postcss@^8.4.43: picocolors "^1.1.1" source-map-js "^1.2.1" +postcss@^8.5.8: + version "8.5.10" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.10.tgz#8992d8c30acf3f12169e7c09514a12fed7e48356" + integrity sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ== + dependencies: + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" @@ -2396,6 +2758,30 @@ reusify@^1.0.4: resolved "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz" integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== +rolldown@1.0.0-rc.15: + version "1.0.0-rc.15" + resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.0.0-rc.15.tgz#ea3526443b2dbe834e9f8f6c1fde6232ec687170" + integrity sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g== + dependencies: + "@oxc-project/types" "=0.124.0" + "@rolldown/pluginutils" "1.0.0-rc.15" + optionalDependencies: + "@rolldown/binding-android-arm64" "1.0.0-rc.15" + "@rolldown/binding-darwin-arm64" "1.0.0-rc.15" + "@rolldown/binding-darwin-x64" "1.0.0-rc.15" + "@rolldown/binding-freebsd-x64" "1.0.0-rc.15" + "@rolldown/binding-linux-arm-gnueabihf" "1.0.0-rc.15" + "@rolldown/binding-linux-arm64-gnu" "1.0.0-rc.15" + "@rolldown/binding-linux-arm64-musl" "1.0.0-rc.15" + "@rolldown/binding-linux-ppc64-gnu" "1.0.0-rc.15" + "@rolldown/binding-linux-s390x-gnu" "1.0.0-rc.15" + "@rolldown/binding-linux-x64-gnu" "1.0.0-rc.15" + "@rolldown/binding-linux-x64-musl" "1.0.0-rc.15" + "@rolldown/binding-openharmony-arm64" "1.0.0-rc.15" + "@rolldown/binding-wasm32-wasi" "1.0.0-rc.15" + "@rolldown/binding-win32-arm64-msvc" "1.0.0-rc.15" + "@rolldown/binding-win32-x64-msvc" "1.0.0-rc.15" + rollup@^2.77.2: version "2.79.2" resolved "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz" @@ -2483,6 +2869,11 @@ shebang-regex@^3.0.0: resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + signal-exit@^4.0.1: version "4.1.0" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" @@ -2555,6 +2946,16 @@ sprintf-js@~1.0.2: resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + +std-env@^4.0.0-rc.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-4.1.0.tgz#45899abc590d86d682e87f0acd1033a75084cd3f" + integrity sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ== + string-argv@~0.3.1: version "0.3.2" resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz" @@ -2584,10 +2985,10 @@ strip-json-comments@^3.1.1, strip-json-comments@~3.1.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strnum@^1.0.5: - version "1.1.2" - resolved "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz" - integrity sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA== +strnum@^2.1.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/strnum/-/strnum-2.2.3.tgz#0119fce02749a11bb126a4d686ac5dbdf6e57586" + integrity sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg== supports-color@^7.1.0: version "7.2.0" @@ -2620,16 +3021,39 @@ term-size@^2.1.0: resolved "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz" integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== -three-mesh-bvh@0.7.0: - version "0.7.0" - resolved "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.7.0.tgz" - integrity sha512-Hj0Z1Rp02yy5H+/xtMBu/dYAeRsSONaBaVLZoST9sMpZxycHypRiUeMHucPOLWFHnpc5hwelOnONcLpkfhDg0Q== +three-mesh-bvh@0.9.9: + version "0.9.9" + resolved "https://registry.yarnpkg.com/three-mesh-bvh/-/three-mesh-bvh-0.9.9.tgz#273be4a3d4a4a287e6be2d947237e47657f84183" + integrity sha512-FJKitcjvbALmeQRK+Sc+nLGorCpkrZBrbgJZFzhdyWboak37DZikn46hvQkNqSbJPm227ahYmS6k3N/GXaAyXw== three@^0.182.0: version "0.182.0" resolved "https://registry.npmjs.org/three/-/three-0.182.0.tgz" integrity sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ== +tinybench@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + +tinyexec@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.1.1.tgz#e1ff45dfa60d1dedb91b734956b78f6c2a3e821b" + integrity sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg== + +tinyglobby@^0.2.15: + version "0.2.16" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.16.tgz#1c3b7eb953fce42b226bc5a1ee06428281aff3d6" + integrity sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.4" + +tinyrainbow@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-3.1.0.tgz#1d8a623893f95cf0a2ddb9e5d11150e191409421" + integrity sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw== + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" @@ -2642,6 +3066,11 @@ ts-api-utils@^1.3.0: resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz" integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== +tslib@^2.4.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + tsx@^4.16.2: version "4.21.0" resolved "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz" @@ -2743,6 +3172,45 @@ vite@^5.2.0: optionalDependencies: fsevents "~2.3.3" +"vite@^6.0.0 || ^7.0.0 || ^8.0.0": + version "8.0.8" + resolved "https://registry.yarnpkg.com/vite/-/vite-8.0.8.tgz#4e26a9bba77c4b27a00b6b10100a7dab48d546a3" + integrity sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw== + dependencies: + lightningcss "^1.32.0" + picomatch "^4.0.4" + postcss "^8.5.8" + rolldown "1.0.0-rc.15" + tinyglobby "^0.2.15" + optionalDependencies: + fsevents "~2.3.3" + +vitest@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-4.1.4.tgz#330a3798ce307f88d3eea373e61a5f14da8f3bb1" + integrity sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg== + dependencies: + "@vitest/expect" "4.1.4" + "@vitest/mocker" "4.1.4" + "@vitest/pretty-format" "4.1.4" + "@vitest/runner" "4.1.4" + "@vitest/snapshot" "4.1.4" + "@vitest/spy" "4.1.4" + "@vitest/utils" "4.1.4" + es-module-lexer "^2.0.0" + expect-type "^1.3.0" + magic-string "^0.30.21" + obug "^2.1.1" + pathe "^2.0.3" + picomatch "^4.0.3" + std-env "^4.0.0-rc.1" + tinybench "^2.9.0" + tinyexec "^1.0.2" + tinyglobby "^0.2.15" + tinyrainbow "^3.1.0" + vite "^6.0.0 || ^7.0.0 || ^8.0.0" + why-is-node-running "^2.3.0" + vue-template-compiler@^2.7.14: version "2.7.16" resolved "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz" @@ -2767,6 +3235,14 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + word-wrap@^1.2.5: version "1.2.5" resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz"