refactor(server-*): refactor client, better state managment
This commit is contained in:
@@ -9,6 +9,10 @@ import type {
|
||||
import type { AuthenticatedPeer, Peer } from './types'
|
||||
|
||||
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 { defineWebSocketHandler, H3 } from 'h3'
|
||||
import { nanoid } from 'nanoid'
|
||||
@@ -50,7 +54,7 @@ const RESPONSES = {
|
||||
}),
|
||||
notAuthenticated: (serverInstanceId: string, parentId?: string) => ({
|
||||
type: 'error',
|
||||
data: { message: 'not authenticated' },
|
||||
data: { message: ServerErrorMessages.notAuthenticated },
|
||||
metadata: createServerEventMetadata(serverInstanceId, parentId),
|
||||
}),
|
||||
error: (message: string, serverInstanceId: string, parentId?: string) => ({
|
||||
@@ -281,7 +285,7 @@ export function setupApp(options?: AppOptions): { app: H3, closeAllPeers: () =>
|
||||
: JSON.parse(text)
|
||||
|
||||
if (!potentialEvent || typeof potentialEvent !== 'object' || !('type' in potentialEvent)) {
|
||||
send(peer, RESPONSES.error('invalid event format', instanceId))
|
||||
send(peer, RESPONSES.error(ServerErrorMessages.invalidEventFormat, instanceId))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -289,7 +293,7 @@ export function setupApp(options?: AppOptions): { app: H3, closeAllPeers: () =>
|
||||
}
|
||||
catch (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
|
||||
}
|
||||
@@ -337,7 +341,7 @@ export function setupApp(options?: AppOptions): { app: H3, closeAllPeers: () =>
|
||||
case 'module:authenticate': {
|
||||
if (authToken && event.data.token !== authToken) {
|
||||
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
|
||||
}
|
||||
@@ -364,24 +368,24 @@ export function setupApp(options?: AppOptions): { app: H3, closeAllPeers: () =>
|
||||
// verify
|
||||
const { name, index, identity } = event.data as { name: string, index?: number, identity?: MetadataEventSource }
|
||||
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
|
||||
}
|
||||
if (typeof index !== 'undefined') {
|
||||
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
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
if (authToken && !p.authenticated) {
|
||||
send(peer, RESPONSES.error('must authenticate before announcing', instanceId))
|
||||
send(peer, RESPONSES.error(ServerErrorMessages.mustAuthenticateBeforeAnnouncing, instanceId))
|
||||
|
||||
return
|
||||
}
|
||||
@@ -420,13 +424,13 @@ export function setupApp(options?: AppOptions): { app: H3, closeAllPeers: () =>
|
||||
const config = data.config
|
||||
|
||||
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
|
||||
}
|
||||
if (typeof moduleIndex !== 'undefined') {
|
||||
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
|
||||
}
|
||||
@@ -442,7 +446,7 @@ export function setupApp(options?: AppOptions): { app: H3, closeAllPeers: () =>
|
||||
})
|
||||
}
|
||||
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
|
||||
|
||||
@@ -4,8 +4,17 @@
|
||||
"lib": [
|
||||
"ESNext"
|
||||
],
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"paths": {
|
||||
"@proj-airi/server-shared": [
|
||||
"../server-shared/src/index.ts"
|
||||
],
|
||||
"@proj-airi/server-shared/*": [
|
||||
"../server-shared/src/*"
|
||||
]
|
||||
},
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
|
||||
@@ -14,9 +14,32 @@ npm i @proj-airi/server-sdk -D
|
||||
```typescript
|
||||
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
|
||||
|
||||
[MIT](../../LICENSE)
|
||||
|
||||
@@ -37,11 +37,9 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@moeru/std": "catalog:",
|
||||
"@proj-airi/server-shared": "workspace:^",
|
||||
"crossws": "^0.4.4",
|
||||
"superjson": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@moeru/std": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
+626
-258
File diff suppressed because it is too large
Load Diff
@@ -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()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
root: import.meta.dirname,
|
||||
test: {
|
||||
environment: 'node',
|
||||
include: ['test/**/*.test.ts'],
|
||||
},
|
||||
})
|
||||
@@ -15,13 +15,17 @@
|
||||
"directory": "packages/server-shared"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.mts",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"./types": {
|
||||
"types": "./dist/types/index.d.mts",
|
||||
"default": "./dist/types/index.mjs"
|
||||
}
|
||||
},
|
||||
"main": "./dist/types/index.mjs",
|
||||
"types": "./dist/types/index.d.mts",
|
||||
"main": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.mts",
|
||||
"files": [
|
||||
"README.md",
|
||||
"dist",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
console.warn('import @proj-airi/server-shared/types instead')
|
||||
export * from './errors'
|
||||
export * from './types'
|
||||
|
||||
@@ -2,6 +2,7 @@ import { defineConfig } from 'tsdown'
|
||||
|
||||
export default defineConfig({
|
||||
entry: {
|
||||
'index': 'src/index.ts',
|
||||
'types/index': 'src/types/index.ts',
|
||||
},
|
||||
sourcemap: true,
|
||||
|
||||
Generated
+8
-34
@@ -1848,7 +1848,7 @@ importers:
|
||||
version: 5.0.0(vue@3.5.29(typescript@5.9.3))
|
||||
'@intlify/unplugin-vue-i18n':
|
||||
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':
|
||||
specifier: ^0.22.4
|
||||
version: 0.22.4(markdown-it@14.1.1)
|
||||
@@ -1908,7 +1908,7 @@ importers:
|
||||
version: 0.1.3
|
||||
unplugin-yaml:
|
||||
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:
|
||||
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)
|
||||
@@ -2233,6 +2233,9 @@ importers:
|
||||
|
||||
packages/server-sdk:
|
||||
dependencies:
|
||||
'@moeru/std':
|
||||
specifier: 'catalog:'
|
||||
version: 0.1.0-beta.17
|
||||
'@proj-airi/server-shared':
|
||||
specifier: workspace:^
|
||||
version: link:../server-shared
|
||||
@@ -2242,10 +2245,6 @@ importers:
|
||||
superjson:
|
||||
specifier: 'catalog:'
|
||||
version: 2.2.6
|
||||
devDependencies:
|
||||
'@moeru/std':
|
||||
specifier: 'catalog:'
|
||||
version: 0.1.0-beta.17
|
||||
|
||||
packages/server-shared:
|
||||
dependencies:
|
||||
@@ -3261,7 +3260,7 @@ importers:
|
||||
version: 14.1.0(vue@3.5.29(typescript@5.9.3))
|
||||
'@wxt-dev/module-vue':
|
||||
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:
|
||||
specifier: ^5.1.6
|
||||
version: 5.1.6
|
||||
@@ -19423,31 +19422,6 @@ snapshots:
|
||||
- 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@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))':
|
||||
dependencies:
|
||||
'@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/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:
|
||||
'@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)
|
||||
transitivePeerDependencies:
|
||||
- vite
|
||||
|
||||
+5
-4
@@ -5,12 +5,13 @@ export default defineConfig({
|
||||
projects: [
|
||||
'apps/server',
|
||||
'apps/stage-tamagotchi',
|
||||
'packages/stage-ui',
|
||||
'packages/plugin-sdk',
|
||||
'packages/cap-vite',
|
||||
'packages/vite-plugin-warpdrive',
|
||||
'packages/audio-pipelines-transcribe',
|
||||
'packages/cap-vite',
|
||||
'packages/plugin-sdk',
|
||||
'packages/server-runtime',
|
||||
'packages/server-sdk',
|
||||
'packages/stage-ui',
|
||||
'packages/vite-plugin-warpdrive',
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user