refactor(server-*): refactor client, better state managment

This commit is contained in:
Neko Ayaka
2026-03-20 21:19:01 +08:00
parent bdce234337
commit 955d1a1d5d
13 changed files with 1129 additions and 314 deletions
+15 -11
View File
@@ -9,6 +9,10 @@ import type {
import type { AuthenticatedPeer, Peer } from './types' import type { AuthenticatedPeer, Peer } from './types'
import { availableLogLevelStrings, Format, LogLevelString, logLevelStringToLogLevelMap, useLogg } from '@guiiai/logg' import { availableLogLevelStrings, Format, LogLevelString, logLevelStringToLogLevelMap, useLogg } from '@guiiai/logg'
import {
createInvalidJsonServerErrorMessage,
ServerErrorMessages,
} from '@proj-airi/server-shared'
import { MessageHeartbeat, MessageHeartbeatKind, WebSocketEventSource } from '@proj-airi/server-shared/types' import { MessageHeartbeat, MessageHeartbeatKind, WebSocketEventSource } from '@proj-airi/server-shared/types'
import { defineWebSocketHandler, H3 } from 'h3' import { defineWebSocketHandler, H3 } from 'h3'
import { nanoid } from 'nanoid' import { nanoid } from 'nanoid'
@@ -50,7 +54,7 @@ const RESPONSES = {
}), }),
notAuthenticated: (serverInstanceId: string, parentId?: string) => ({ notAuthenticated: (serverInstanceId: string, parentId?: string) => ({
type: 'error', type: 'error',
data: { message: 'not authenticated' }, data: { message: ServerErrorMessages.notAuthenticated },
metadata: createServerEventMetadata(serverInstanceId, parentId), metadata: createServerEventMetadata(serverInstanceId, parentId),
}), }),
error: (message: string, serverInstanceId: string, parentId?: string) => ({ error: (message: string, serverInstanceId: string, parentId?: string) => ({
@@ -281,7 +285,7 @@ export function setupApp(options?: AppOptions): { app: H3, closeAllPeers: () =>
: JSON.parse(text) : JSON.parse(text)
if (!potentialEvent || typeof potentialEvent !== 'object' || !('type' in potentialEvent)) { if (!potentialEvent || typeof potentialEvent !== 'object' || !('type' in potentialEvent)) {
send(peer, RESPONSES.error('invalid event format', instanceId)) send(peer, RESPONSES.error(ServerErrorMessages.invalidEventFormat, instanceId))
return return
} }
@@ -289,7 +293,7 @@ export function setupApp(options?: AppOptions): { app: H3, closeAllPeers: () =>
} }
catch (err) { catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err) const errorMessage = err instanceof Error ? err.message : String(err)
send(peer, RESPONSES.error(`invalid JSON, error: ${errorMessage}`, instanceId)) send(peer, RESPONSES.error(createInvalidJsonServerErrorMessage(errorMessage), instanceId))
return return
} }
@@ -337,7 +341,7 @@ export function setupApp(options?: AppOptions): { app: H3, closeAllPeers: () =>
case 'module:authenticate': { case 'module:authenticate': {
if (authToken && event.data.token !== authToken) { if (authToken && event.data.token !== authToken) {
logger.withFields({ peer: peer.id, peerRemote: peer.remoteAddress, peerRequest: peer.request.url }).log('authentication failed') logger.withFields({ peer: peer.id, peerRemote: peer.remoteAddress, peerRequest: peer.request.url }).log('authentication failed')
send(peer, RESPONSES.error('invalid token', instanceId, event.metadata?.event.id)) send(peer, RESPONSES.error(ServerErrorMessages.invalidToken, instanceId, event.metadata?.event.id))
return return
} }
@@ -364,24 +368,24 @@ export function setupApp(options?: AppOptions): { app: H3, closeAllPeers: () =>
// verify // verify
const { name, index, identity } = event.data as { name: string, index?: number, identity?: MetadataEventSource } const { name, index, identity } = event.data as { name: string, index?: number, identity?: MetadataEventSource }
if (!name || typeof name !== 'string') { if (!name || typeof name !== 'string') {
send(peer, RESPONSES.error('the field \'name\' must be a non-empty string for event \'module:announce\'', instanceId)) send(peer, RESPONSES.error(ServerErrorMessages.moduleAnnounceNameInvalid, instanceId))
return return
} }
if (typeof index !== 'undefined') { if (typeof index !== 'undefined') {
if (!Number.isInteger(index) || index < 0) { if (!Number.isInteger(index) || index < 0) {
send(peer, RESPONSES.error('the field \'index\' must be a non-negative integer for event \'module:announce\'', instanceId)) send(peer, RESPONSES.error(ServerErrorMessages.moduleAnnounceIndexInvalid, instanceId))
return return
} }
} }
if (!identity || identity.kind !== 'plugin' || !identity.plugin?.id) { if (!identity || identity.kind !== 'plugin' || !identity.plugin?.id) {
send(peer, RESPONSES.error('module identity must include kind=plugin and a plugin id for event \'module:announce\'', instanceId)) send(peer, RESPONSES.error(ServerErrorMessages.moduleAnnounceIdentityInvalid, instanceId))
return return
} }
if (authToken && !p.authenticated) { if (authToken && !p.authenticated) {
send(peer, RESPONSES.error('must authenticate before announcing', instanceId)) send(peer, RESPONSES.error(ServerErrorMessages.mustAuthenticateBeforeAnnouncing, instanceId))
return return
} }
@@ -420,13 +424,13 @@ export function setupApp(options?: AppOptions): { app: H3, closeAllPeers: () =>
const config = data.config const config = data.config
if (moduleName === '') { if (moduleName === '') {
send(peer, RESPONSES.error('the field \'moduleName\' can\'t be empty for event \'ui:configure\'', instanceId)) send(peer, RESPONSES.error(ServerErrorMessages.uiConfigureModuleNameInvalid, instanceId))
return return
} }
if (typeof moduleIndex !== 'undefined') { if (typeof moduleIndex !== 'undefined') {
if (!Number.isInteger(moduleIndex) || moduleIndex < 0) { if (!Number.isInteger(moduleIndex) || moduleIndex < 0) {
send(peer, RESPONSES.error('the field \'moduleIndex\' must be a non-negative integer for event \'ui:configure\'', instanceId)) send(peer, RESPONSES.error(ServerErrorMessages.uiConfigureModuleIndexInvalid, instanceId))
return return
} }
@@ -442,7 +446,7 @@ export function setupApp(options?: AppOptions): { app: H3, closeAllPeers: () =>
}) })
} }
else { else {
send(peer, RESPONSES.error('module not found, it hasn\'t announced itself or the name is incorrect', instanceId)) send(peer, RESPONSES.error(ServerErrorMessages.moduleNotFound, instanceId))
} }
return return
+9
View File
@@ -4,8 +4,17 @@
"lib": [ "lib": [
"ESNext" "ESNext"
], ],
"baseUrl": ".",
"module": "ESNext", "module": "ESNext",
"moduleResolution": "bundler", "moduleResolution": "bundler",
"paths": {
"@proj-airi/server-shared": [
"../server-shared/src/index.ts"
],
"@proj-airi/server-shared/*": [
"../server-shared/src/*"
]
},
"esModuleInterop": true, "esModuleInterop": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"isolatedModules": true, "isolatedModules": true,
+24 -1
View File
@@ -14,9 +14,32 @@ npm i @proj-airi/server-sdk -D
```typescript ```typescript
import { Client } from '@proj-airi/server-sdk' import { Client } from '@proj-airi/server-sdk'
const c = new Client({ name: 'your airi plugin' }) const client = new Client({
name: 'your airi plugin',
autoConnect: false,
})
await client.connect()
client.onEvent('input:text', async (event) => {
console.info(event.data.text)
})
``` ```
`connect()` now resolves when the client is fully ready for use, not just when the websocket transport has opened. In practice that means:
- the socket is open
- authentication succeeded when a token is configured
- the module has announced itself successfully
Useful runtime helpers:
- `client.connectionStatus` exposes the current lifecycle state
- `client.isReady` tells you whether the client has completed authentication + announce
- `client.send()` returns `false` instead of silently dropping messages when the socket is unavailable
- `client.sendOrThrow()` is available when you want strict delivery semantics
- `client.onEvent()` returns an unsubscribe function
## License ## License
[MIT](../../LICENSE) [MIT](../../LICENSE)
+1 -3
View File
@@ -37,11 +37,9 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@moeru/std": "catalog:",
"@proj-airi/server-shared": "workspace:^", "@proj-airi/server-shared": "workspace:^",
"crossws": "^0.4.4", "crossws": "^0.4.4",
"superjson": "catalog:" "superjson": "catalog:"
},
"devDependencies": {
"@moeru/std": "catalog:"
} }
} }
File diff suppressed because it is too large Load Diff
+256
View File
@@ -0,0 +1,256 @@
import type { WebSocketEvent } from '@proj-airi/server-shared/types'
import superjson from 'superjson'
import { afterEach, describe, expect, it, vi } from 'vitest'
class MockWebSocket {
static readonly CONNECTING = 0
static readonly OPEN = 1
static readonly CLOSING = 2
static readonly CLOSED = 3
static instances: MockWebSocket[] = []
readonly sent: string[] = []
readyState = MockWebSocket.CONNECTING
onclose?: () => void
onerror?: (event: { error?: Error }) => void
onmessage?: (event: { data: string }) => void
onopen?: () => void
constructor(public readonly url: string) {
MockWebSocket.instances.push(this)
}
send(data: string) {
this.sent.push(data)
}
close() {
this.readyState = MockWebSocket.CLOSED
this.onclose?.()
}
ping() {}
pong() {}
}
vi.mock('crossws/websocket', () => ({
default: MockWebSocket,
}))
const { Client } = await import('../src/client')
function lastSocket() {
const socket = MockWebSocket.instances.at(-1)
if (!socket) {
throw new Error('No mock websocket instance created')
}
return socket
}
function parseSent(socket: MockWebSocket, index = -1) {
const payload = socket.sent.at(index)
if (!payload) {
throw new Error(`No sent payload at index ${index}`)
}
return superjson.parse<WebSocketEvent>(payload)
}
function emitOpen(socket: MockWebSocket) {
socket.readyState = MockWebSocket.OPEN
socket.onopen?.()
}
function emitMessage(socket: MockWebSocket, event: WebSocketEvent) {
socket.onmessage?.({
data: superjson.stringify(event),
})
}
afterEach(() => {
MockWebSocket.instances.length = 0
vi.useRealTimers()
})
describe('client', () => {
it('resolves connect only after authentication and self announcement', async () => {
const client = new Client({
autoConnect: false,
autoReconnect: false,
name: 'test-plugin',
token: 'secret',
})
const connected = client.connect()
const socket = lastSocket()
emitOpen(socket)
expect(parseSent(socket)).toMatchObject({
type: 'module:authenticate',
data: { token: 'secret' },
})
emitMessage(socket, {
type: 'module:authenticated',
data: { authenticated: true },
metadata: {
source: { kind: 'plugin', plugin: { id: 'server' }, id: 'server-1' },
event: { id: 'auth-1' },
},
})
const announceEvent = parseSent(socket)
expect(announceEvent).toMatchObject({
type: 'module:announce',
data: { name: 'test-plugin' },
})
emitMessage(socket, {
type: 'module:announced',
data: {
name: 'test-plugin',
identity: announceEvent.data.identity,
},
metadata: {
source: { kind: 'plugin', plugin: { id: 'server' }, id: 'server-1' },
event: { id: 'announce-1' },
},
})
await expect(connected).resolves.toBeUndefined()
expect(client.connectionStatus).toBe('ready')
expect(client.isReady).toBe(true)
})
it('fails terminally on invalid token', async () => {
const client = new Client({
autoConnect: false,
autoReconnect: true,
name: 'test-plugin',
token: 'wrong-token',
})
const connected = client.connect()
const socket = lastSocket()
emitOpen(socket)
emitMessage(socket, {
type: 'error',
data: { message: 'invalid token' },
metadata: {
source: { kind: 'plugin', plugin: { id: 'server' }, id: 'server-1' },
event: { id: 'error-1' },
},
})
await expect(connected).rejects.toThrow('invalid token')
expect(client.connectionStatus).toBe('failed')
})
it('returns an unsubscribe function from onEvent', () => {
const client = new Client({
autoConnect: false,
autoReconnect: false,
name: 'test-plugin',
})
const listener = vi.fn()
const dispose = client.onEvent('input:text', listener)
dispose()
expect(() => client.offEvent('input:text', listener)).not.toThrow()
})
it('supports timeout-aware ensureConnected without cancelling the shared connect task', async () => {
vi.useFakeTimers()
const client = new Client({
autoConnect: false,
autoReconnect: false,
name: 'test-plugin',
})
const timedOut = client.ensureConnected({ timeout: 50 })
const timedOutAssertion = expect(timedOut).rejects.toThrow('Connection timed out after 50ms')
const socket = lastSocket()
await vi.advanceTimersByTimeAsync(50)
await timedOutAssertion
emitOpen(socket)
const announceEvent = parseSent(socket)
emitMessage(socket, {
type: 'module:announced',
data: {
name: 'test-plugin',
identity: announceEvent.data.identity,
},
metadata: {
source: { kind: 'plugin', plugin: { id: 'server' }, id: 'server-1' },
event: { id: 'announce-1' },
},
})
await expect(client.ensureConnected()).resolves.toBeUndefined()
expect(client.isReady).toBe(true)
})
it('supports abort-aware connect', async () => {
const client = new Client({
autoConnect: false,
autoReconnect: false,
name: 'test-plugin',
})
const controller = new AbortController()
const connecting = client.connect({ abortSignal: controller.signal })
lastSocket()
controller.abort()
await expect(connecting).rejects.toThrow('Connection aborted')
expect(client.connectionStatus).toBe('connecting')
})
it('notifies external state listeners', async () => {
const client = new Client({
autoConnect: false,
autoReconnect: false,
name: 'test-plugin',
})
const listener = vi.fn()
const dispose = client.onConnectionStateChange(listener)
const connected = client.connect()
const socket = lastSocket()
emitOpen(socket)
const announceEvent = parseSent(socket)
emitMessage(socket, {
type: 'module:announced',
data: {
name: 'test-plugin',
identity: announceEvent.data.identity,
},
metadata: {
source: { kind: 'plugin', plugin: { id: 'server' }, id: 'server-1' },
event: { id: 'announce-1' },
},
})
await connected
expect(listener).toHaveBeenCalledWith({ previousStatus: 'idle', status: 'connecting' })
expect(listener).toHaveBeenCalledWith({ previousStatus: 'connecting', status: 'announcing' })
expect(listener).toHaveBeenCalledWith({ previousStatus: 'announcing', status: 'ready' })
dispose()
})
})
+9
View File
@@ -0,0 +1,9 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
root: import.meta.dirname,
test: {
environment: 'node',
include: ['test/**/*.test.ts'],
},
})
+6 -2
View File
@@ -15,13 +15,17 @@
"directory": "packages/server-shared" "directory": "packages/server-shared"
}, },
"exports": { "exports": {
".": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"./types": { "./types": {
"types": "./dist/types/index.d.mts", "types": "./dist/types/index.d.mts",
"default": "./dist/types/index.mjs" "default": "./dist/types/index.mjs"
} }
}, },
"main": "./dist/types/index.mjs", "main": "./dist/index.mjs",
"types": "./dist/types/index.d.mts", "types": "./dist/index.d.mts",
"files": [ "files": [
"README.md", "README.md",
"dist", "dist",
+167
View File
@@ -0,0 +1,167 @@
export const ServerErrorMessages = {
invalidEventFormat: 'invalid event format',
invalidToken: 'invalid token',
mustAuthenticateBeforeAnnouncing: 'must authenticate before announcing',
moduleAnnounceIdentityInvalid: 'module identity must include kind=plugin and a plugin id for event \'module:announce\'',
moduleAnnounceIndexInvalid: 'the field \'index\' must be a non-negative integer for event \'module:announce\'',
moduleAnnounceNameInvalid: 'the field \'name\' must be a non-empty string for event \'module:announce\'',
moduleNotFound: 'module not found, it hasn\'t announced itself or the name is incorrect',
notAuthenticated: 'not authenticated',
uiConfigureModuleIndexInvalid: 'the field \'moduleIndex\' must be a non-negative integer for event \'ui:configure\'',
uiConfigureModuleNameInvalid: 'the field \'moduleName\' can\'t be empty for event \'ui:configure\'',
} as const
export type ServerErrorCode
= | 'invalid-event-format'
| 'invalid-json'
| 'invalid-token'
| 'module-announce-identity-invalid'
| 'module-announce-index-invalid'
| 'module-announce-name-invalid'
| 'module-not-found'
| 'must-authenticate-before-announcing'
| 'not-authenticated'
| 'ui-configure-module-index-invalid'
| 'ui-configure-module-name-invalid'
| 'unknown'
export interface ParsedServerErrorMessage {
authentication: boolean
code: ServerErrorCode
message: string
recoverable: boolean
terminal: boolean
}
export function createInvalidJsonServerErrorMessage(errorMessage: string) {
return `invalid JSON, error: ${errorMessage}`
}
export function parseServerErrorMessage(message: string): ParsedServerErrorMessage {
if (message === ServerErrorMessages.invalidToken) {
return {
authentication: true,
code: 'invalid-token',
message,
recoverable: false,
terminal: true,
}
}
if (message === ServerErrorMessages.notAuthenticated) {
return {
authentication: true,
code: 'not-authenticated',
message,
recoverable: true,
terminal: false,
}
}
if (message === ServerErrorMessages.mustAuthenticateBeforeAnnouncing) {
return {
authentication: true,
code: 'must-authenticate-before-announcing',
message,
recoverable: true,
terminal: false,
}
}
if (message === ServerErrorMessages.invalidEventFormat) {
return {
authentication: false,
code: 'invalid-event-format',
message,
recoverable: false,
terminal: false,
}
}
if (message.startsWith('invalid JSON, error: ')) {
return {
authentication: false,
code: 'invalid-json',
message,
recoverable: false,
terminal: false,
}
}
if (message === ServerErrorMessages.moduleAnnounceNameInvalid) {
return {
authentication: false,
code: 'module-announce-name-invalid',
message,
recoverable: false,
terminal: false,
}
}
if (message === ServerErrorMessages.moduleAnnounceIndexInvalid) {
return {
authentication: false,
code: 'module-announce-index-invalid',
message,
recoverable: false,
terminal: false,
}
}
if (message === ServerErrorMessages.moduleAnnounceIdentityInvalid) {
return {
authentication: false,
code: 'module-announce-identity-invalid',
message,
recoverable: false,
terminal: false,
}
}
if (message === ServerErrorMessages.moduleNotFound) {
return {
authentication: false,
code: 'module-not-found',
message,
recoverable: false,
terminal: false,
}
}
if (message === ServerErrorMessages.uiConfigureModuleNameInvalid) {
return {
authentication: false,
code: 'ui-configure-module-name-invalid',
message,
recoverable: false,
terminal: false,
}
}
if (message === ServerErrorMessages.uiConfigureModuleIndexInvalid) {
return {
authentication: false,
code: 'ui-configure-module-index-invalid',
message,
recoverable: false,
terminal: false,
}
}
return {
authentication: false,
code: 'unknown',
message,
recoverable: false,
terminal: false,
}
}
export function isAuthenticationServerErrorMessage(message: string) {
return parseServerErrorMessage(message).authentication
}
export function isTerminalAuthenticationServerErrorMessage(message: string) {
const parsed = parseServerErrorMessage(message)
return parsed.authentication && parsed.terminal
}
+2 -1
View File
@@ -1 +1,2 @@
console.warn('import @proj-airi/server-shared/types instead') export * from './errors'
export * from './types'
+1
View File
@@ -2,6 +2,7 @@ import { defineConfig } from 'tsdown'
export default defineConfig({ export default defineConfig({
entry: { entry: {
'index': 'src/index.ts',
'types/index': 'src/types/index.ts', 'types/index': 'src/types/index.ts',
}, },
sourcemap: true, sourcemap: true,
+8 -34
View File
@@ -1848,7 +1848,7 @@ importers:
version: 5.0.0(vue@3.5.29(typescript@5.9.3)) version: 5.0.0(vue@3.5.29(typescript@5.9.3))
'@intlify/unplugin-vue-i18n': '@intlify/unplugin-vue-i18n':
specifier: ^11.0.7 specifier: ^11.0.7
version: 11.0.7(@vue/compiler-dom@3.5.29)(eslint@9.39.3(jiti@2.6.1))(rollup@4.59.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.14)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)) version: 11.0.7(@vue/compiler-dom@3.5.29)(eslint@9.39.3(jiti@2.6.1))(rollup@4.59.0)(typescript@5.9.3)(vite@8.0.0-beta.15(@types/node@24.10.14)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))
'@mdit/plugin-footnote': '@mdit/plugin-footnote':
specifier: ^0.22.4 specifier: ^0.22.4
version: 0.22.4(markdown-it@14.1.1) version: 0.22.4(markdown-it@14.1.1)
@@ -1908,7 +1908,7 @@ importers:
version: 0.1.3 version: 0.1.3
unplugin-yaml: unplugin-yaml:
specifier: ^4.0.0 specifier: ^4.0.0
version: 4.0.0(@nuxt/kit@3.20.2(magicast@0.5.2))(esbuild@0.27.2)(rolldown@1.0.0-rc.3)(rollup@4.59.0)(vite@7.3.1(@types/node@24.10.14)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) version: 4.0.0(esbuild@0.27.2)(rolldown@1.0.0-rc.3)(rollup@4.59.0)(vite@8.0.0-beta.15(@types/node@24.10.14)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
vitepress: vitepress:
specifier: ^2.0.0-alpha.16 specifier: ^2.0.0-alpha.16
version: 2.0.0-alpha.16(@types/node@24.10.14)(change-case@5.4.4)(fuse.js@7.1.0)(idb-keyval@6.2.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(nprogress@0.2.0)(postcss@8.5.6)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) version: 2.0.0-alpha.16(@types/node@24.10.14)(change-case@5.4.4)(fuse.js@7.1.0)(idb-keyval@6.2.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(nprogress@0.2.0)(postcss@8.5.6)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
@@ -2233,6 +2233,9 @@ importers:
packages/server-sdk: packages/server-sdk:
dependencies: dependencies:
'@moeru/std':
specifier: 'catalog:'
version: 0.1.0-beta.17
'@proj-airi/server-shared': '@proj-airi/server-shared':
specifier: workspace:^ specifier: workspace:^
version: link:../server-shared version: link:../server-shared
@@ -2242,10 +2245,6 @@ importers:
superjson: superjson:
specifier: 'catalog:' specifier: 'catalog:'
version: 2.2.6 version: 2.2.6
devDependencies:
'@moeru/std':
specifier: 'catalog:'
version: 0.1.0-beta.17
packages/server-shared: packages/server-shared:
dependencies: dependencies:
@@ -3261,7 +3260,7 @@ importers:
version: 14.1.0(vue@3.5.29(typescript@5.9.3)) version: 14.1.0(vue@3.5.29(typescript@5.9.3))
'@wxt-dev/module-vue': '@wxt-dev/module-vue':
specifier: ^1.0.3 specifier: ^1.0.3
version: 1.0.3(vite@7.3.1(@types/node@24.10.14)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))(wxt@0.20.18(@types/node@24.10.14)(canvas@3.2.1)(eslint@9.39.3(jiti@2.6.1))(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(rollup@4.59.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) version: 1.0.3(vite@8.0.0-beta.15(@types/node@24.10.14)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))(wxt@0.20.18(@types/node@24.10.14)(canvas@3.2.1)(eslint@9.39.3(jiti@2.6.1))(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(rollup@4.59.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
nanoid: nanoid:
specifier: ^5.1.6 specifier: ^5.1.6
version: 5.1.6 version: 5.1.6
@@ -19423,31 +19422,6 @@ snapshots:
- supports-color - supports-color
- typescript - typescript
'@intlify/unplugin-vue-i18n@11.0.7(@vue/compiler-dom@3.5.29)(eslint@9.39.3(jiti@2.6.1))(rollup@4.59.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.14)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1))
'@intlify/bundle-utils': 11.0.7(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))
'@intlify/shared': 11.2.8
'@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.2.8)(@vue/compiler-dom@3.5.29)(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))
'@rollup/pluginutils': 5.3.0(rollup@4.59.0)
'@typescript-eslint/scope-manager': 8.56.1
'@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3)
debug: 4.4.3
fast-glob: 3.3.3
pathe: 2.0.3
picocolors: 1.1.1
unplugin: 2.3.11
vite: 7.3.1(@types/node@24.10.14)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
vue: 3.5.29(typescript@5.9.3)
optionalDependencies:
vue-i18n: 11.2.8(vue@3.5.29(typescript@5.9.3))
transitivePeerDependencies:
- '@vue/compiler-dom'
- eslint
- rollup
- supports-color
- typescript
'@intlify/unplugin-vue-i18n@11.0.7(@vue/compiler-dom@3.5.29)(eslint@9.39.3(jiti@2.6.1))(rollup@4.59.0)(typescript@5.9.3)(vite@8.0.0-beta.15(@types/node@24.10.14)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))': '@intlify/unplugin-vue-i18n@11.0.7(@vue/compiler-dom@3.5.29)(eslint@9.39.3(jiti@2.6.1))(rollup@4.59.0)(typescript@5.9.3)(vite@8.0.0-beta.15(@types/node@24.10.14)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))':
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1)) '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1))
@@ -23682,9 +23656,9 @@ snapshots:
'@types/filesystem': 0.0.36 '@types/filesystem': 0.0.36
'@types/har-format': 1.2.16 '@types/har-format': 1.2.16
'@wxt-dev/module-vue@1.0.3(vite@7.3.1(@types/node@24.10.14)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))(wxt@0.20.18(@types/node@24.10.14)(canvas@3.2.1)(eslint@9.39.3(jiti@2.6.1))(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(rollup@4.59.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': '@wxt-dev/module-vue@1.0.3(vite@8.0.0-beta.15(@types/node@24.10.14)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))(wxt@0.20.18(@types/node@24.10.14)(canvas@3.2.1)(eslint@9.39.3(jiti@2.6.1))(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(rollup@4.59.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
dependencies: dependencies:
'@vitejs/plugin-vue': 6.0.4(vite@7.3.1(@types/node@24.10.14)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3)) '@vitejs/plugin-vue': 6.0.4(vite@8.0.0-beta.15(@types/node@24.10.14)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))
wxt: 0.20.18(@types/node@24.10.14)(canvas@3.2.1)(eslint@9.39.3(jiti@2.6.1))(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(rollup@4.59.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) wxt: 0.20.18(@types/node@24.10.14)(canvas@3.2.1)(eslint@9.39.3(jiti@2.6.1))(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(rollup@4.59.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
transitivePeerDependencies: transitivePeerDependencies:
- vite - vite
+5 -4
View File
@@ -5,12 +5,13 @@ export default defineConfig({
projects: [ projects: [
'apps/server', 'apps/server',
'apps/stage-tamagotchi', 'apps/stage-tamagotchi',
'packages/stage-ui',
'packages/plugin-sdk',
'packages/cap-vite',
'packages/vite-plugin-warpdrive',
'packages/audio-pipelines-transcribe', 'packages/audio-pipelines-transcribe',
'packages/cap-vite',
'packages/plugin-sdk',
'packages/server-runtime', 'packages/server-runtime',
'packages/server-sdk',
'packages/stage-ui',
'packages/vite-plugin-warpdrive',
], ],
}, },
}) })