@be-enlighten/enspace-sdk-vue
v0.11.2
Published
Vue 3 composables/plugin and Nuxt 4 module for the Enspace SDK.
Downloads
1,204
Maintainers
Readme
@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 pusher-js # camada /realtime
pnpm add ai @ai-sdk/vue # camada /aivue@^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, pusher-js 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:
hooksnão é aceito. A config é publicada emruntimeConfig.publice funções não sobrevivem à serialização. Para observabilidade HTTP, usesetupEnspaceou construa o client comcreateEnspace.api-keyebearerexpõem a credencial ao browser. A config vai pararuntimeConfig.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 fetchComposables 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/admin/workspace), plans (admin/workspace) e communications (notificações, tipos, 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(inputunknown): passe o terceiro genérico com a shape do estado,useEnspaceForm<typeof schema, Task, FormState>({ ... }). - Com Nuxt UI, mapeie
fieldErrorspara a prop:errorsdoUForm({ 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 cascatalist() ≡ list({}) ≡ list({ campo: undefined }), e workspace(id).root ≡ workspaces.byId(id).
Realtime (/realtime)
Importe de @be-enlighten/enspace-sdk-vue/realtime. Requer realtime: { appKey, host, port } no setup do plugin (ou enspace.realtime no nuxt.config.ts) e pusher-js 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:
AiChatErrorHandlermapeia códigos comoinsufficient_balancepara 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.
