@be-enlighten/enspace-sdk-vue

Vue 3 composables/plugin and Nuxt 4 module for the Enspace SDK.
3,348
Install
npm install @be-enlighten/enspace-sdk-vue
Latest Version:0.17.0
Vue:3
License:MIT
Last Updated:Sep 25, 2026
Links: npm
Maintainer: hardeath950

@be-enlighten/enspace-sdk-vue

Adapter Vue 3 e Nuxt 4 para o @be-enlighten/enspace-sdk-core.

Entrypoint O que entrega
@be-enlighten/enspace-sdk-vue Plugin, setup com Keycloak e composables base (useEnspace, useEnspaceScoped, useAuth).
.../query Data layer sobre Pinia Colada: cache, dedup, mutations com invalidação, workspace ativo e formulários.
.../realtime Composables de eventos ao vivo (canais, eventos de usuário, notificações).
.../ai Composables para widgets de chat de AI (sessão com streaming, seleções, upload).
.../nuxt Módulo Nuxt 4 com auto-import.

Instalação

pnpm add @be-enlighten/enspace-sdk-vue

pnpm add pinia @pinia/colada   # camada /query
pnpm add socket.io-client   # camada /realtime
pnpm add ai @ai-sdk/vue        # camada /ai

vue@^3.5 é o único peer obrigatório. Todos os outros são opcionais e só necessários para a camada correspondente: pinia + @pinia/colada para a /query, socket.io-client para a /realtime, ai + @ai-sdk/vue para a /ai, nuxt + @nuxt/kit para o módulo /nuxt.

Setup — Vue 3

Com api-key ou bearer o caminho é síncrono:

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { PiniaColada } from '@pinia/colada'
import { EnspacePlugin } from '@be-enlighten/enspace-sdk-vue'
import App from './App.vue'

const app = createApp(App)

// Pinia e Pinia Colada ANTES do EnspacePlugin — a /query depende deles.
app.use(createPinia())
app.use(PiniaColada)
app.use(EnspacePlugin, {
  baseUrl: import.meta.env.VITE_ENSPACE_BASE_URL,
  auth: { type: 'api-key', key: import.meta.env.VITE_ENSPACE_API_KEY },
})

app.mount('#app')

Com Keycloak, use setupEnspace antes do mount:

import { setupEnspace } from '@be-enlighten/enspace-sdk-vue'

const { client, keycloak } = await setupEnspace(app, {
  baseUrl: import.meta.env.VITE_ENSPACE_BASE_URL,
  keycloak: {
    url: import.meta.env.VITE_KEYCLOAK_URL,
    realm: import.meta.env.VITE_KEYCLOAK_REALM,
    clientId: import.meta.env.VITE_KEYCLOAK_CLIENT_ID,
  },
})

app.mount('#app')

O init do keycloak-js é assíncrono e o app.use() do Vue descarta o retorno do install, então app.use(EnspacePlugin, { keycloak }) lança um erro apontando para setupEnspace. Se o init do Keycloak falhar (servidor fora, silent check sem retorno), a app monta em modo degradado não autenticado: os provides existem, useAuth() funciona e login() redireciona para o SSO.

Para auth sem Keycloak também existe setupEnspaceSync(app, options), que rejeita a opção keycloak.

Adicione o helper de silent check ao public/ da app:

cp node_modules/@be-enlighten/enspace-sdk-vue/dist/auth/silent-check-sso.html public/

Opções do plugin

Além de todos os campos de EnspaceConfig do core (baseUrl, auth, workspace, language, retry, timeoutMs, hooks, realtime):

Opção Descrição
keycloak Config do SSO por redirect. Mutuamente exclusiva com auth.
keycloak.onSessionExpired Política quando a sessão SSO morre de verdade (refresh rejeitado pelo IdP): 'login' (default — redireciona para o Keycloak), 'emit' (só sinaliza em useAuth().sessionExpired) ou 'none'.
workspacePersistence Persistência do workspace ativo: { persist, storageKey, storage }.

O SDK também faz refresh proativo quando a aba volta a ficar visível (visibilitychange): após suspensão/background, o token é reavaliado antes da próxima request, em vez de descobrir a sessão morta só no primeiro 401.

app.use(EnspacePlugin, {
  baseUrl,
  auth,
  workspacePersistence: { persist: true, storageKey: 'app:ws', storage: sessionStorage },
})

Setup — Nuxt 4

export default defineNuxtConfig({
  modules: ['@be-enlighten/enspace-sdk-vue/nuxt'],
  enspace: {
    baseUrl: process.env.NUXT_PUBLIC_ENSPACE_BASE_URL!,
    auth: {
      type: 'keycloak',
      url: process.env.NUXT_PUBLIC_KEYCLOAK_URL!,
      realm: process.env.NUXT_PUBLIC_KEYCLOAK_REALM!,
      clientId: process.env.NUXT_PUBLIC_KEYCLOAK_CLIENT_ID!,
    },
    // Opcional — opções browser-only do keycloak-js (serializáveis):
    keycloakBrowser: {
      checkLoginIframe: false, // session-iframe legacy quebra com cookies 3rd-party
      onSessionExpired: 'login', // 'login' (default) | 'emit' | 'none'
    },
  },
})

O módulo instala @pinia/nuxt e @pinia/colada-nuxt, registra o plugin client e auto-importa os composables do adapter mais enspaceKeys, toFieldErrors, enspaceChannels e realtimeEvents do core. Config inválida falha no build, não no primeiro request.

Duas restrições do módulo:

  • hooks não é aceito. A config é publicada em runtimeConfig.public e funções não sobrevivem à serialização. Para observabilidade HTTP, use setupEnspace ou construa o client com createEnspace.
  • api-key e bearer expõem a credencial ao browser. A config vai para runtimeConfig.public, portanto para o payload HTML e o bundle client. Use apenas credenciais de escopo limitado, nunca com privilégio de admin.

SSR

O plugin do módulo roda em modo client: os provides do SDK não existem durante o render no servidor. useEnspace(), useEnspaceScoped(), useAuth() e toda a /query dependem do provide e não funcionam em SSR. Em páginas com SSR ligado, envolva os componentes que os usam em <ClientOnly> ou desligue o SSR da página (ssr: false).

Composables base

useEnspace()

Retorna o EnspaceClient injetado.

<script setup lang="ts">
import { useEnspace } from '@be-enlighten/enspace-sdk-vue'

const client = useEnspace()
const profile = await client.account.getProfile()
</script>

useEnspaceScoped({ workspace })

WorkspaceScope reativo, sem mutar o client. Aceita workspace como string, Ref ou getter.

import { ref } from 'vue'
import { useEnspaceScoped } from '@be-enlighten/enspace-sdk-vue'

const activeWorkspace = ref<string | undefined>('ws_abc')
const { client, scope } = useEnspaceScoped({ workspace: activeWorkspace })

// scope.value muda automaticamente quando activeWorkspace muda
await scope.value?.members.list()

useAuth()

Disponível quando o setup recebeu a opção keycloak.

import { useAuth } from '@be-enlighten/enspace-sdk-vue'

const {
  isAuthenticated, // ComputedRef<boolean>
  token, // ComputedRef<string | undefined>
  user, // ComputedRef<{ id, username, email? } | undefined>
  roles, // ComputedRef<string[]>
  sessionExpired, // ComputedRef<boolean> — sessão SSO morreu de verdade
  login, // (options?) => Promise<void>
  logout, // (options?) => Promise<void>
  updateToken, // (minValidity?: number) => Promise<string | undefined>
  hasRoles, // (roles: string[]) => boolean
} = useAuth()

await login({ redirectUri: `${location.origin}/dashboard` })
if (hasRoles(['admin'])) { /* ... */ }

A strategy injeta Authorization: Bearer <token> em cada request e faz refresh automático quando necessário. Se a sessão SSO morrer no IdP (refresh rejeitado), a política onSessionExpired decide a reação (default: redirect para o login) e sessionExpired sinaliza o estado para a UI.

Data layer (/query)

Importe de @be-enlighten/enspace-sdk-vue/query. O EnspaceClient continua sendo um objeto JS puro, sem cache nem reatividade: a /query é a camada reativa sobre ele.

useWorkspace() — workspace ativo

Estado compartilhado e instanciado uma vez: lista cacheada, activeReference persistido e dedup embutido.

import { useWorkspace } from '@be-enlighten/enspace-sdk-vue/query'

const { workspaces, activeReference, activeWorkspace, scope, setActive, ensureActive } = useWorkspace()

onMounted(() => ensureActive()) // N chamadas concorrentes = 1 fetch

Composables de resource

import { useCreateTask, useDeleteTask, useTasksList, useUpdateTask } from '@be-enlighten/enspace-sdk-vue/query'

// Listagem: cache, dedup e refetch automático quando os params mudam.
// Desabilitada sozinha enquanto não há workspace ativo, sem guard manual.
const { data: tasks, isLoading, refresh } = useTasksList({ _limit: 50, status: 'pending' })

// Mutations invalidam as queries afetadas e aguardam o refetch,
// então a UI nunca pisca dado velho.
const { mutateAsync: createTask, asyncStatus } = useCreateTask()
await createTask({ name: 'Revisar proposta' })

A cobertura acompanha os resources do client: account, apiKeys, workspaces, members, memberGroups, invites, roles, dictionaries, modelViews, uploads, types (+ items/fields), workflows (+ .executions/.versions/.nodes/.logs), ai (chats, inference, agents, models, documents, review), tasks, financial (user/workspace), plans (workspace) e communications (notificações, comments e threads).

useEnspaceQuery / useEnspaceMutation

Para qualquer chamada sem composable dedicado, combine o foundation com enspaceKeys do core:

import { enspaceKeys } from '@be-enlighten/enspace-sdk-core'
import { useEnspaceQuery, useWorkspace } from '@be-enlighten/enspace-sdk-vue/query'

const { activeReference } = useWorkspace()

const { data: groups } = useEnspaceQuery({
  key: () => enspaceKeys.workspace(activeReference.value!).memberGroups.list({ _limit: 50 }),
  query: ({ client }) =>
    client.workspaces.workspace(activeReference.value!).memberGroups.list({ _limit: 50 }),
  enabled: () => activeReference.value !== undefined,
})
import { useEnspaceMutation } from '@be-enlighten/enspace-sdk-vue/query'

const { mutateAsync } = useEnspaceMutation({
  mutation: (id: number, { client }) =>
    client.workspaces.workspace(wsId).roles.delete(id),
  invalidate: () => enspaceKeys.workspace(wsId).roles.root, // prefixo: invalida lists + items
  // awaitInvalidation: false → invalida em background
})

useEnspaceForm

Validação client com o schema da própria API, re-exportado pelo pacote de schemas, mais erros do servidor mapeados para campos automaticamente:

import { TaskCreateRequest } from '@be-enlighten/enspace-sdk-schemas'
import { useEnspaceForm } from '@be-enlighten/enspace-sdk-vue/query'

const { state, saving, fieldErrors, formErrors, fieldError, submit, reset } = useEnspaceForm({
  schema: TaskCreateRequest.pick({ name: true, priority: true, due_date: true }),
  initial: { name: '', priority: 'normal', due_date: '' },
  submit: data => createTask(data), // ValidationError da API vira fieldErrors
})

// Template: v-model="state.name", :error="fieldError('name')", :loading="saving"
  • O erro de um campo limpa sozinho quando o campo é editado.
  • Campos com z.coerce/z.preprocess (input unknown): passe o terceiro genérico com a shape do estado, useEnspaceForm<typeof schema, Task, FormState>({ ... }).
  • Com Nuxt UI, mapeie fieldErrors para a prop :errors do UForm ({ name, message }[]).

Cache keys

enspaceKeys vem do core e é hierárquica: invalidar um prefixo invalida tudo abaixo dele.

import { enspaceKeys } from '@be-enlighten/enspace-sdk-core'

enspaceKeys.tasks.lists() // todas as listagens de tasks
enspaceKeys.workspace('ws_1').tasks.list({ _limit: 50 })
enspaceKeys.workspace('ws_1').tasks.root // lists + count + items
enspaceKeys.workspaces.byId('ws_1') // tudo do workspace, em cascata

list() ≡ list({}) ≡ list({ campo: undefined }), e workspace(id).root ≡ workspaces.byId(id).

Realtime (/realtime)

Importe de @be-enlighten/enspace-sdk-vue/realtime. Requer realtime: { url } no setup do plugin (ou enspace.realtime no nuxt.config.ts) e socket.io-client instalado. Sem config de realtime os composables são no-op com um warn único: nada quebra.

Todos são SSR-safe e fazem cleanup no unmount.

useRealtimeNotifications()

Notificações ao vivo do usuário autenticado. Invalida o cache de notificações da /query a cada evento. Chame uma vez, no layout raiz.

import { useRealtimeNotifications } from '@be-enlighten/enspace-sdk-vue/realtime'

const toast = useToast()

useRealtimeNotifications({
  onNotification: n => toast.add({ title: n.title, description: n.body ?? undefined }),
  onRead: ({ references }) => console.debug('lidas', references),
  onDismissed: ({ reference }) => console.debug('dispensada', reference),
})

Opções: enabled (default true), invalidate (default true), throttleMs (default 1000, janela só da invalidação de cache), onNotification, onRead, onDismissed. Retorna { enabled, state, isBound, lastEventAt }. Callbacks nunca são throttleados, então toasts saem na hora.

useRealtimeChannel()

Canal privado de entidade, com nome reativo.

import { enspaceChannels, enspaceKeys, realtimeEvents } from '@be-enlighten/enspace-sdk-core'
import { useWorkspace } from '@be-enlighten/enspace-sdk-vue/query'
import { useRealtimeChannel } from '@be-enlighten/enspace-sdk-vue/realtime'
import { useQueryCache } from '@pinia/colada'

const { activeReference } = useWorkspace()
const queryCache = useQueryCache()

const { channelName, isSubscribed, lastEventAt } = useRealtimeChannel({
  channel: () => activeReference.value ? enspaceChannels.tasksList(activeReference.value) : undefined,
  events: realtimeEvents.tasks,
  onEvent: () => void queryCache.invalidateQueries({
    key: enspaceKeys.workspace(activeReference.value!).tasks.root,
  }),
})

Opções: channel (obrigatório; null/undefined deixa o composable em espera), events (obrigatório, estático por instância), onEvent, enabled (default true), throttleMs (default 3000, leading + trailing; 0 desativa). O subscribe acontece quando channel resolve e enabled é true; a troca de nome ou enabled: false derruba a assinatura.

useRealtimeUserEvents()

Eventos do canal do usuário autenticado, sem nome de canal.

import { realtimeEvents } from '@be-enlighten/enspace-sdk-core'
import { useRealtimeUserEvents } from '@be-enlighten/enspace-sdk-vue/realtime'

const { isBound, lastEventAt } = useRealtimeUserEvents({
  events: [realtimeEvents.user.workspaceJoined, realtimeEvents.user.workspaceRemoved],
  onEvent: (event, payload) => console.log(event, payload),
})

useRealtimeClient()

Estado reativo da conexão, para indicadores de UI. Não conecta por conta própria.

const { realtime, enabled, state } = useRealtimeClient()
// state.value → { connection, connected, signedIn }

AI (/ai)

Importe de @be-enlighten/enspace-sdk-vue/ai. Composables para construir widgets de chat sobre o resource ai do core:

  • Sessão com streaming: useAiChatSession + EnspaceChatTransport.
  • Erros: AiChatErrorHandler mapeia códigos como insufficient_balance para mensagens localizadas.
  • Seleções por turno: useAiModelSelection, useAiModeSelection, useAiReasoningSelection, useAiAgentsSelection.
  • Anexos: useAiChatUpload.
  • Utilitários de UI: auto-scroll, smooth-stream e agrupamento do histórico por data.

Pressupõe a /query instalada. Os peers ai e @ai-sdk/vue são opcionais.

Tipos

Re-exports type-only do core:

import type { AuthConfig, EnspaceClient, EnspaceConfig, RequestOptions, RetryConfig } from '@be-enlighten/enspace-sdk-vue'

Para tipos de domínio (User, Workspace, Member, ...), importe de @be-enlighten/enspace-sdk-schemas ou do core.

Licença

MIT.

Related Packages

@truewatchtech/browser-rum-nuxt

TrueWatch Browser SDK RUM Nuxt integration

2,449 0
@cloudcare/browser-rum-nuxt

Guance Browser SDK RUM Nuxt integration

2,399 54
@frontic/nuxt

Nuxt module for Frontic - data fetching composables with smart caching, page rou...

8,386
@authon/vue

Authon Vue 3 SDK — plugin, composables, and components

2,797 1
comfortable-nuxt

Nuxt Module of the Comfortable Javascript - SDK

1,599 0

Version History

Version Released License
0.17.0 MIT
0.16.0 MIT
0.15.0 MIT
0.14.0 MIT
0.13.0 MIT