@be-enlighten/enspace-sdk-core
v0.11.2
Published
Core client for the Enspace API — HTTP, auth strategies, typed errors, resources.
Maintainers
Readme
@be-enlighten/enspace-sdk-core
Cliente HTTP da API do Enspace, agnóstico de framework. Funciona em Node, browser e edge runtimes.
Entrega autenticação (API key, bearer, Keycloak), erros tipados, resources de domínio com tipos derivados da API, streaming SSE, realtime opcional e utilitários para camadas de cache (enspaceKeys) e formulários (toFieldErrors).
Instalação
pnpm add @be-enlighten/enspace-sdk-coreRealtime é opt-in e depende de um peer opcional:
pnpm add pusher-jsQuick start
import { createEnspace } from '@be-enlighten/enspace-sdk-core'
const enspace = createEnspace({
baseUrl: process.env.ENSPACE_BASE_URL!,
auth: { type: 'api-key', key: process.env.ENSPACE_API_KEY! },
})
const me = await enspace.account.getProfile()
const workspaces = await enspace.workspaces.list()
// Escopo de workspace por chamada, sem mutar o client:
const members = await enspace.workspaces
.workspace(workspaces[0]!.reference)
.members.list()Configuração
createEnspace(config: EnspaceConfig, strategy?: AuthStrategy): EnspaceClient| Campo | Tipo | Descrição |
|---|---|---|
| baseUrl | string | Raiz da API. Obrigatório. |
| auth | AuthConfig | União discriminada. Obrigatório. Ver Autenticação. |
| workspace | string? | Workspace ativo. Quando setado, todo request envia en-workspace. |
| language | string? | Locale enviado como Accept-Language (ex.: pt-BR). Omitido quando ausente. |
| retry | RetryConfig? | Política de retry. Ver Retry. |
| timeoutMs | number? | Timeout por request. Default 30000. |
| hooks | HttpHooks? | Callbacks de observabilidade. Ver Observabilidade. |
| realtime | RealtimeConfig? | Habilita enspace.realtime. Ver Realtime. |
baseUrl e auth ausentes lançam Error na criação.
Bodies de respostas 2xx têm strings ISO 8601 revividas como Date, casando com os tipos dos schemas: o tipo diz Date e o runtime entrega Date.
EnspaceClient
| Resource | O que é |
|---|---|
| account | Perfil do usuário autenticado. |
| apiKeys | API Keys do usuário (user-level). |
| workspaces | Workspace atual + atalho workspace(id). |
| members | Membros do workspace ativo. |
| memberGroups | Grupos de usuários. |
| invites | Lifecycle de convites (autenticado + público). |
| roles | Roles + permissões por role. |
| dictionaries | Lexicons de tradução. |
| modelViews | Views (kanban/table). |
| uploads | Upload de arquivos (File/Blob → multipart). |
| types | Content types + type(slug).{items,fields}. |
| workflows | Workflows + .executions/.versions/.nodes/.logs. |
| ai | .chats/.inference/.agents/.models/.documents/.review, com streaming SSE. |
| tasks | Tarefas do workspace + listFormatted(). |
| financial | .user/.admin/.workspace(id) — carteiras e pedidos de crédito. |
| plans | .admin/.workspace(id) — catálogo de planos, assinaturas e quotas. |
| communications | .user/.admin/.workspace(id) — notificações, comments e threads. |
itemsefieldsnão são propriedades do client: a rota exige o slug do type. Useenspace.types.type(slug).itemseenspace.types.type(slug).fields.
| Método | O que faz |
|---|---|
| setWorkspace(id) | Define o workspace ativo (header en-workspace). |
| getWorkspace() | Retorna o id ativo ou undefined. |
| login(credentials) | Login interativo (apenas Keycloak). |
| logout() | Limpa tokens e derruba a conexão de realtime. |
| request<T>(method, path, options?) | Request crua pelo mesmo pipeline (auth, retry, erros). |
| stream(method, path, options?) | Request SSE. Retorna ReadableStream<Uint8Array>. |
| getTokenStore() | Acesso read-only ao TokenStore. |
| realtime | EnspaceRealtimeClient (lazy). Ver Realtime. |
| realtimeEnabled | boolean — se config.realtime foi fornecido. |
Autenticação
api-key
auth: { type: 'api-key', key: 'ens_…' }Header x-api-key em toda request. Sem refresh.
bearer
auth: { type: 'bearer', token: 'eyJ…' }Header Authorization: Bearer <token>. Sem refresh.
keycloak
auth: {
type: 'keycloak',
url: process.env.KEYCLOAK_URL!,
realm: 'enspace',
clientId: 'enspace-app',
credentials: { username, password }, // opcional — login lazy
}Resource Owner Password (Direct Grant), com refresh proativo (buffer de 30s), dedup de refresh concorrente, retry automático após refresh e re-login como fallback.
external
Para plugar uma strategy própria, como a KeycloakBrowserStrategy do adapter Vue (SSO por redirect). O segundo argumento de createEnspace passa a ser obrigatório:
import { createEnspace } from '@be-enlighten/enspace-sdk-core'
import { KeycloakBrowserStrategy } from '@be-enlighten/enspace-sdk-vue'
const enspace = createEnspace(
{ baseUrl, auth: { type: 'external' } },
new KeycloakBrowserStrategy(keycloakInstance),
)Workspace scoping
Duas formas equivalentes:
// 1) Set once — todas as requests carregam o header
enspace.setWorkspace('ws_abc')
await enspace.members.list()
// 2) Per-call — sem mutar o client (útil para multi-tenant)
await enspace.workspaces.workspace('ws_abc').members.list()Domain resources
account
await enspace.account.getProfile()
await enspace.account.updateProfile({ fullname: 'Ada Lovelace' })
await enspace.account.updatePassword({ password: 'new-secret-123' })Métodos: getProfile(), updateProfile(body), updatePassword(body), prepareForCompatibility(version, workspace).
apiKeys
User-level: independente de workspace.
const { key } = await enspace.apiKeys.create({ description: 'CI' })
// `key` é exibida apenas uma vez — persista imediatamente.
const keys = await enspace.apiKeys.list()
await enspace.apiKeys.delete(keys[0]!.id)Métodos: list(), count(), findOne(id), create(body), update(id, body), delete(id).
workspaces
const list = await enspace.workspaces.list()
const ctx = await enspace.workspaces.context()
const stats = await enspace.workspaces.stats()
enspace.workspaces.setActive('ws_abc')
await enspace.workspaces.workspace('ws_abc').context()Métodos: list(), context(), stats(), create(body), updateSettings(body), delete(reference), leave(), checkReference(reference), setActive(id), workspace(id).
members
await enspace.members.list({ status: 'active' })
await enspace.members.create({ email: '[email protected]', type: 'standard' })Métodos: list(), count(), findOne(id), create(body), update(id, body), delete(id), cleanDuplicates(), findDuplicates(), resendInvite(body), resendInvites(), sync(), syncPreview().
memberGroups
const group = await enspace.memberGroups.create({ name: 'Engineering' })
await enspace.memberGroups.addUsers(group.id, { userIds: [1, 2, 3] })
await enspace.memberGroups.removeUsers(group.id, { userIds: [2] })Métodos: list(), count(), findOne(id), create(body), update(id, body), delete(id), addUsers(id, body), removeUsers(id, body).
invites
const invites = await enspace.invites.list()
const info = await enspace.invites.getInfo('inv_tok_abc') // rota pública
await enspace.invites.acceptPublic('inv_tok_abc') // rota pública
await enspace.invites.accept('ws_abc') // rota autenticadaMétodos: list(), requestResend() (reenvia os convites pendentes do workspace, sem body), accept(workspace), cancel(workspace), getInfo(token), acceptPublic(token).
roles
const role = await enspace.roles.create({ name: 'Editor' })
await enspace.roles.addPermission(role.id, { action: 'read', subject: 'items' })
const perms = await enspace.roles.listPermissions(role.id)
await enspace.roles.removePermission(role.id, perms[0]!.id)Métodos: list(), count(), findOne(id), create(body), update(id, body), delete(id), listPermissions(roleId), addPermission(roleId, body), updatePermission(roleId, permissionId, body), removePermission(roleId, permissionId), findDuplicatePermissions(roleId).
dictionaries
const ptBR = await enspace.dictionaries.findByLocale('pt-BR')
await enspace.dictionaries.create({ locale: 'pt-BR', words: { hello: 'olá' } })Métodos: list(), count(), keys() (retorna DictionaryKeysTree), findByLocale(locale), findOne(id), create(body), update(id, body), delete(id).
modelViews
const views = await enspace.modelViews.list()
await enspace.modelViews.create({ model: 'contratos', model_type: 'kanban' })Métodos: list(), count(), findOne(id), create(body), update(id, body), delete(id).
uploads
upload() monta o FormData internamente a partir de File/Blob (um arquivo ou um array) e retorna UploadFile[], um por arquivo enviado. Esse é o mesmo formato (UploadFileValue) do valor de campos uploadFile/uploadImage/EnPDF em itens.
const uploaded = await enspace.uploads.upload(file, { path: 'documents/contracts' })
await enspace.uploads.getSignedUrl(uploaded[0]!.id)
// Múltiplos arquivos + vínculo com um registro de entidade
await enspace.uploads.upload([fileA, blobB], {
fileName: 'anexo.pdf',
related: { ref: 'c-document-templates', refId: '42', field: 'file' },
})Opções viram campos multipart: path (sub-path lógico), fieldRef/type/data/userInfo (fluxo EnPDF), related (vínculo com entidade) e fileName (fallback para Blob sem nome).
Filtros de list(): path exato, path_prefix (navegação hierárquica), name_contains, mime_contains.
Métodos: list(), count(), findOne(id), upload(files, options?), delete(id), getSignedUrl(id).
types, items e fields
const ct = await enspace.types.create({ name: 'Contratos', slug: 'contratos' })
const scope = enspace.types.type('contratos')
// scope.items e scope.fields têm o slug pré-boundMétodos de types: list(), count(), findOne(slug), create(body), update(slug, body), delete(slug), type(slug).
const items = await enspace.types.type('contratos').items.list({ _limit: 20 })
await enspace.types.type('contratos').items.create({ data: { title: 'X' } })
await enspace.types.type('contratos').items.versions('ref_123')
const fields = await enspace.types.type('contratos').fields.list()
await enspace.types.type('contratos').fields.create({
refId: 'priority',
type: 'EnlDropdown',
label: 'Prioridade',
})Métodos de items: list(), count(), findOne({ reference }), create(body), update({ reference, data }), delete({ reference }), restore({ reference }), duplicate({ reference }), versions(reference), scoped(options), form(identification).
Métodos de fields: list(), findOne(id), create(body), update(id, body), delete(id). refId e type são imutáveis.
workflows
const wf = await enspace.workflows.create({ name: 'Approval', version: '1.0.0', nodes: [], edges: [] })
await enspace.workflows.start(wf.id)
await enspace.workflows.executions.list({ _limit: 10 })
await enspace.workflows.nodes.retry(failedNodeId)
await enspace.workflows.webhookTrigger('wh_ref', 'wh.sk.secret', { payload: 1 })Métodos: list(), count(), findOne(id), create(body), update(id, body), delete(id), restore(id), start(id, body?), metrics(id, params?), workspaceMetrics(params?), resyncCron(id), getWebhookToken(id), webhookTrigger(reference, token, body?).
Sub-resources: .executions, .versions, .nodes, .logs.
ai
const chat = await enspace.ai.chats.create({ input: 'Olá' })
const { text } = await enspace.ai.inference.generateText({ prompt: 'Resuma' })
const { object } = await enspace.ai.inference.generateObject({
prompt: 'Extraia dados',
fields: [{ refId: 'name', type: 'text', label: 'Nome' }],
})
const { inference, agent } = await enspace.ai.models.list()
const doc = await enspace.ai.documents.download('doc_abc')
const stats = await enspace.ai.review.stats.get().chats:list,findOne,create,update,delete,messages,deleteMessage,clearMessages,respond(reference, body, signal?)→ streaming..inference:generateText,generateObject,editorStream(body, signal?)→ streaming de texto..agents:list,findOne,create,update,delete,restore,tools,testAgent(body, signal?)→ streaming..models:list()→{ inference, agent }(modelos permitidos por contexto)..documents:download(reference)→ signed URL + metadata..review:.playbooks(list,findOne,create,update,delete),.sessions(list,findOne,delete),.stats(get()).
Streaming SSE
respond, testAgent e editorStream retornam AsyncIterable<T>, não Promise<T>. Passe um AbortSignal para cancelar o stream em andamento:
const ctrl = new AbortController()
setTimeout(() => ctrl.abort(), 5000)
for await (const chunk of enspace.ai.chats.respond('chat_ref', body, ctrl.signal)) {
console.log(chunk)
}tasks
const task = await enspace.tasks.create({ name: 'Revisar', priority: 'high' })
await enspace.tasks.update(task.id, { status: 'completed' })
const formatted = await enspace.tasks.listFormatted({ status: 'pending', _limit: 20 })Métodos: list(), listFormatted(body?), count(), findOne(id | reference), create(body), update(id, body), delete(id), restore(id).
financial
Três escopos, expostos como namespaces:
// User — operações do próprio usuário, sem header de workspace
await enspace.financial.user.listWallets()
await enspace.financial.user.createCreditRequest({ amount: 100, reason: 'Onboarding' })
await enspace.financial.user.cancelCreditRequest(42)
// Admin — plataforma toda, requer permissão admin
await enspace.financial.admin.listCreditRequests({ status: 'pending' })
await enspace.financial.admin.approveCreditRequest(7, { reviewer_note: 'OK' })
await enspace.financial.admin.createWalletTransaction(walletId, {
type: 'credit', amount: 50, description: 'Bonus',
})
await enspace.financial.admin.listInvoices({ origin: 'renewal' })
await enspace.financial.admin.voidInvoice(9)
// Workspace — vinculado a um workspace
await enspace.financial.workspace('ws_abc').getWallet()
await enspace.workspaces.workspace('ws_abc').financial.listCreditRequests()
await enspace.workspaces.workspace('ws_abc').financial.listInvoices()
await enspace.workspaces.workspace('ws_abc').financial.findInvoice('inv_ref') // com itensCredit requests: listCreditRequests, countCreditRequests, findCreditRequest, createCreditRequest, cancelCreditRequest (user/workspace) ou approveCreditRequest/rejectCreditRequest (admin). Wallets: listWallets, countWallets, findWallet, createWallet (user) e getWallet (workspace). Transações: listWalletTransactions, countWalletTransactions, createWalletTransaction (admin). Invoices (faturas de cobrança do plano): listInvoices, findInvoice (workspace, por reference, com itens) e listInvoices, countInvoices, findInvoice (por id, com itens), voidInvoice (admin).
plans
Dois escopos: planos pertencem ao workspace, não ao usuário.
// Admin — catálogo da plataforma
const plan = await enspace.plans.admin.createPlan({
name: 'Basic', code: 'basic', interval: 'monthly', price_credits: 12300,
})
await enspace.plans.admin.createProduct({ code: 'ai_credits', name: 'Créditos de IA', aggregation: 'sum' })
await enspace.plans.admin.addPlanProduct(plan.id, { product: productId, included_units: 1000, allow_overage: true })
await enspace.plans.admin.createSubscription({ workspaceId: 180, planCode: 'basic' })
// Workspace — catálogo público, assinatura e quotas
const plans = await enspace.plans.workspace('ws_abc').listPlans()
const sub = await enspace.plans.workspace('ws_abc').getCurrentSubscription()
const quotas = await enspace.plans.workspace('ws_abc').listQuotas()
await enspace.plans.workspace('ws_abc').upgrade({ plan_code: 'standard' }).admin: planos (listPlans, countPlans, findPlan, createPlan, updatePlan, deletePlan — deprecia, nunca hard-delete), produtos (listProducts, countProducts, findProduct, createProduct, updateProduct, deleteProduct), plan-products (listPlanProducts, addPlanProduct — body com product, o plan vem da URL, updatePlanProduct, deletePlanProduct), plan-features (listPlanFeatures, addPlanFeature, updatePlanFeature, deletePlanFeature), subscriptions (listSubscriptions, countSubscriptions, findSubscription, createSubscription, updateSubscription — despache upgrade/downgrade/cancel_pending via action, cancelSubscription), inspeção (getWorkspaceQuotas(workspaceId), getFeaturesRegistry()) e operações em lote (runRenewal(), runReconciliation(), requeueBlockedEmails(body?) — reenvio em massa de e-mails bloqueados por quota).
.workspace(id): listPlans() (catálogo ativo em projeção pública), getFeatures() (feature flags efetivas do plano ativo), listQuotas()/getQuota(product) (used/remaining/limit por produto), getCurrentSubscription(), previewPlanChange(planCode) (classificação upgrade/downgrade + proration + data efetiva), upgrade(body) (imediato, com cobrança prorrateada), downgrade(body) (agendado para o fim do período, seta pending_plan) e cancelPendingChange().
communications
Três escopos, no mesmo padrão de financial:
// User — notificações e preferências do usuário autenticado
await enspace.communications.user.listNotifications({ read: 'false' })
await enspace.communications.user.countUnreadNotifications()
await enspace.communications.user.markNotificationsRead({ references: ['n_1'] })
await enspace.communications.user.markAllNotificationsRead()
await enspace.communications.user.dismissNotification('n_1')
await enspace.communications.user.updateNotificationPreference('comment.mention', { enabled: false })
// Admin — tipos de notificação da plataforma
await enspace.communications.admin.listNotificationTypes({ active: 'true' })
await enspace.communications.admin.createNotificationType({ key: 'comment.mention', name: 'Menção' })
// Workspace — comments e threads
await enspace.communications.workspace('ws_abc')
.listComments({ entity: 'c-items', entity_reference: '42' }) // entity/entity_reference obrigatórios
await enspace.communications.workspace('ws_abc')
.createThread({ title: 'Revisão', type: 'discussion' })
await enspace.communications.workspace('ws_abc')
.addThreadMessage('t_1', { content: 'Primeira mensagem' })User: listNotifications, listNotificationsExpanded (com type e actor populados), countNotifications, countUnreadNotifications, findNotification, findNotificationExpanded, markNotificationsRead, markAllNotificationsRead, dismissNotification, listNotificationPreferences, updateNotificationPreference.
Admin: listNotificationTypes, countNotificationTypes, findNotificationType, createNotificationType, updateNotificationType, deleteNotificationType.
Workspace: comments (listComments, countComments, findComment, createComment, updateComment, deleteComment), threads (listThreads, countThreads, findThreadsByEntity, findThread, createThread, updateThread, deleteThread) e messages (listThreadMessages, countThreadMessages, addThreadMessage, updateThreadMessage, deleteThreadMessage).
Realtime
Eventos ao vivo sobre Pusher/Soketi. Opt-in: sem config.realtime, enspace.realtimeEnabled é false e o getter enspace.realtime lança EnspaceError com code: 'realtime.not_configured'.
Requer pusher-js@^8 instalado (peer opcional, carregado por import dinâmico no primeiro connect()).
const enspace = createEnspace({
baseUrl: process.env.ENSPACE_BASE_URL!,
auth: { type: 'bearer', token },
realtime: {
appKey: process.env.ENSPACE_REALTIME_APP_KEY!,
host: process.env.ENSPACE_REALTIME_HOST,
port: 6001,
},
})| Campo | Default | Descrição |
|---|---|---|
| appKey | — | Obrigatório. Chave pública da app de websocket. |
| host / port | — | Host e porta do servidor de websocket. |
| cluster | 'mt1' | Cluster Pusher. |
| useTLS | false | Força wss. |
| authEndpoint | ${baseUrl}/websocket/auth | Autorização de canais privados. |
| userAuthEndpoint | ${baseUrl}/websocket/auth/user | Autenticação do canal de usuário. |
| autoSignin | true | Habilita eventos de usuário. |
| logToConsole | false | Logs do cliente Pusher. |
A autorização passa pelo mesmo pipeline HTTP do SDK: headers da strategy ativa, refresh automático de token e erros tipados.
import { enspaceChannels, realtimeEvents } from '@be-enlighten/enspace-sdk-core'
import type { EntityEventPayload } from '@be-enlighten/enspace-sdk-core'
const rt = enspace.realtime
await rt.connect()
// Canal privado de entidade
const off = await rt.bindChannel(
enspaceChannels.tasksList('acme-corp'),
'task.created',
(payload: EntityEventPayload) => console.log(payload.entity),
)
// Canal do usuário autenticado
const offUser = await rt.onUserEvent(
realtimeEvents.user.notificationCreated,
notification => console.log(notification),
)
off()
offUser()
rt.disconnect()Métodos: connect(), whenConnected(), disconnect(), subscribe(channel), unsubscribe(channel), bindChannel(channel, event, handler), onUserEvent(event, handler), on(event, handler), getState(), onStateChange(handler). Os binders retornam a função de cleanup.
enspaceChannels monta os nomes de canal — nunca concatene a string na mão:
| Builder | Argumentos |
|---|---|
| itemsList | (workspaceRef, typeSlug) |
| item | (workspaceRef, typeSlug, itemRef) |
| tasksList | (workspaceRef) |
| task | (workspaceRef, taskRef) |
| flowItemTasksList | (workspaceRef) |
| flowItemTask | (workspaceRef, taskId) |
| thread | (workspaceRef, threadRef) |
| comments | (workspaceRef, entityModel, entityRef) |
| workspaceLegacy | (workspaceRef) |
realtimeEvents é o catálogo de eventos: items, tasks, flowItemTasks, members, comments, threads (arrays de nomes) e user (objeto com notificationCreated, notificationRead, notificationDismissed, workspaceJoined, workspaceMemberUpdated, workspaceRemoved).
Códigos de erro: realtime.not_configured, realtime.missing_app_key, realtime.pusher_js_missing, realtime.connection_failed, realtime.user_channel_unavailable.
Em Vue e Nuxt, use os composables do subpath /realtime do @be-enlighten/enspace-sdk-vue em vez de manipular o client direto.
Erros
Tudo deriva de EnspaceError, sempre com code, status e details?.
| Classe | Status | Quando |
|---|---|---|
| NetworkError | 0 | DNS, conexão recusada, timeout. |
| AuthError | 401/403 | Token inválido ou expirado. |
| ValidationError | 400/422 | Body inválido. |
| NotFoundError | 404 | Recurso não existe. |
| EnspaceError | outros | 5xx e fallback. |
import { AuthError, isRetryable, NotFoundError, ValidationError } from '@be-enlighten/enspace-sdk-core'
try {
await enspace.members.create({ email: 'invalid' })
}
catch (e) {
if (e instanceof ValidationError) console.error(e.code, e.details)
else if (e instanceof NotFoundError) console.error(e.message)
else if (e instanceof AuthError) console.warn('Reautentique')
else if (e instanceof Error && isRetryable(e)) console.warn('Transiente — retry')
}Retry
5xx,429eNetworkErrorsão retentados atéretry.maxAttempts(default3).401/403disparam uma retentativa após refresh do token (Keycloak).- O header
Retry-After(429/503) é honrado como delay mínimo e exposto emEnspaceError.retryAfterMs.
createEnspace({
baseUrl,
auth,
retry: {
maxAttempts: 4,
backoff: 'exponential', // ou 'linear'
baseDelayMs: 500,
retryMethods: 'idempotent', // default 'all'
},
})retryMethods: 'idempotent' retenta apenas GET/HEAD/OPTIONS/PUT/DELETE. Use quando um 5xx em POST/PATCH puder ter persistido o write no servidor.
Erros de formulário
toFieldErrors normaliza ValidationError (400/422) e ZodError para Record<campo, mensagens[]>:
import { toFieldErrors, ValidationError } from '@be-enlighten/enspace-sdk-core'
try {
await enspace.tasks.create({ name: '' })
}
catch (err) {
if (err instanceof ValidationError) {
toFieldErrors(err) // { name: ['String must contain at least 1 character'] }
}
}Cobre o formato de erro da API, variantes com Zod issues cruas e o ZodError de um schema.safeParse client-side. Retorna {} para erros que não são de validação, que devem ser tratados como erro global.
Query params
Todo list() e count() aceita filtros por campo, operadores (_lt, _lte, _gt, _gte, _ne, _in, _nin, _contains, _ncontains, _containss, _ncontainss, _null), paginação (_limit, _start), sort (_sort), busca (_q) e a cláusula composta _where com grupos _or/_and/_nor aninháveis.
await enspace.members.list({
status: 'active',
_limit: 20,
_start: 0,
_sort: 'created_at:desc',
_q: 'ana',
_where: {
_or: [
{ role_in: [1, 2] },
{ created_at_gte: '2026-01-01T00:00:00.000Z' },
],
},
})Os params são tipados por modelo via FindQuery<T>/CountQuery<T>, aliases sobre EnlightenQueryParams<T> do @be-enlighten/enspace-sdk-schemas. Valores aceitam T[P] | string, porque a query string é serializada: envie datas em ISO 8601 ('2026-07-21T00:00:00.000Z'), booleans como 'true' | 'false' e números em decimal.
Cache keys
enspaceKeys é uma factory hierárquica de keys para camadas de cache (Pinia Colada, TanStack Query, SWR). TypeScript puro, zero dependência de runtime. Invalidar um prefixo invalida tudo abaixo dele.
import { enspaceKeys } from '@be-enlighten/enspace-sdk-core'
enspaceKeys.tasks.root // ['enspace', 'tasks'] — tudo de tasks
enspaceKeys.tasks.lists() // só listagens
enspaceKeys.tasks.list({ _limit: 50 }) // listagem com params
enspaceKeys.tasks.count({ status: 'pending' }) // contagens
enspaceKeys.tasks.byId('t_1') // registro único
// Sub-resources aninham sob o mesmo prefixo
enspaceKeys.types.items('contratos').byId('i_9')
enspaceKeys.workflows.versions('wf_1').lists()
// Workspace-scoped: `workspace(id).root` é exatamente `workspaces.byId(id)`,
// então invalidar o workspace cascateia para todo o cache escopado nele.
enspaceKeys.workspace('ws_1').tasks.list()Keys são normalizadas contra fragmentação: list() ≡ list({}) ≡ list({ campo: undefined }). Todos os resources são cobertos, incluindo especializações como roles.permissions(id), dictionaries.byLocale(locale), invites.byToken(token), types.itemVersions(ref), ai.review.*, financial.* e communications.*.
Observabilidade
config.hooks recebe callbacks read-only disparados por tentativa de request (retries disparam novamente). Use para logging, tracing e métricas: mutar options não altera o comportamento do request.
createEnspace({
baseUrl,
auth,
hooks: {
onRequest: ctx => console.debug(ctx.options.method, ctx.request),
onResponseError: ctx => reportError(ctx.response?.status, ctx.request),
},
})Erros lançados dentro de um hook são engolidos e nunca quebram o request. Hooks são funções, portanto não serializáveis: no adapter Nuxt, que lê a config de runtimeConfig, use setupEnspace do adapter Vue ou construa o client com createEnspace.
Licença
MIT.
