@cosmospro/msw-odata
v1.0.1
Published
Biblioteca com a implementação odata para utilização dentro do MSW, permitindo mock de apis odata para desenvolvimento.
Maintainers
Readme
@cosmospro/msw-odata
Biblioteca para mockar endpoints OData dentro do MSW (Mock Service Worker) — análoga ao helper graphql.* que o MSW oferece para GraphQL, porém voltada a APIs OData (v4 e v2). Oferece uma API fluent que, em poucas linhas, gera handlers MSW cobrindo todos os verbos HTTP (GET, POST, PUT, PATCH, DELETE, MERGE) sobre uma coleção, com suporte a autenticação, envelope OData real e store in-memory com reset.
Framework-agnostic — funciona em projetos React, Angular, Vue, Svelte ou Vanilla JS.
🚀 Instalação
npm install --save-dev @cosmospro/msw-odata mswRequisitos: Node.js ≥ 18, MSW ≥ 2.0
⚡ Início rápido — API fluent
A forma recomendada de usar a lib. Um único método (mockOData) gera handlers MSW cobrindo todos os verbos HTTP sobre a coleção:
import { setupWorker } from 'msw/browser'
import { mockOData } from '@cosmospro/msw-odata'
const users = [
{ id: '1', name: 'João Silva', status: 'active' },
{ id: '2', name: 'Maria Santos', status: 'inactive' },
]
export const worker = setupWorker(
...mockOData('/api/users', users).handlers(),
)Com essas três linhas você ganha:
| Rota | Verbo | Comportamento |
|---------------------|---------|--------------------------------------------------------------|
| /api/users | GET | Lista com $filter, $orderby, $top, $skip, $select, $count |
| /api/users | POST | Insere no store, retorna 201 |
| /api/users(:key) | GET | Retorna item ou 404 |
| /api/users(:key) | PUT | Replace completo, 200 |
| /api/users(:key) | PATCH | Merge parcial, 200 |
| /api/users(:key) | MERGE | Alias de PATCH (OData v2 legacy) |
| /api/users(:key) | DELETE | Remove do store, 204 |
Resposta de GET /api/users:
{
"@odata.context": "$metadata#users",
"@odata.count": 2,
"value": [
{ "id": "1", "name": "João Silva", "status": "active" },
{ "id": "2", "name": "Maria Santos", "status": "inactive" }
]
}🔐 Autenticação — Bearer token
mockOData('/api/users', users)
.withAuth.bearer('secret-token-123')
.handlers()Requests sem o header Authorization: Bearer secret-token-123 recebem 401 automaticamente. Você também pode passar uma função validadora:
mockOData('/api/users', users)
.withAuth.bearer((token) => token.startsWith('prefix_'))
.handlers()Para predicados totalmente customizados (qualquer header, cookie, etc.):
mockOData('/api/users', users)
.withAuth((request) => request.headers.get('x-api-key') === 'abc')
.handlers()🎯 Override por verbo — on*
Intercepte um verbo específico mantendo o comportamento default nos demais. Use ctx.next() para delegar à implementação padrão:
mockOData('/api/users', users)
.onPost(async (ctx) => {
const body = await ctx.request.clone().json()
if (!body.email) return ctx.reply.badRequest('email required')
return ctx.next() // delega ao POST default
})
.onDelete((ctx) => ctx.reply.forbidden()) // nunca permite delete
.handlers()O objeto ctx recebido por cada override contém:
| Campo | Tipo | Descrição |
|-------------|-------------------|---------------------------------------------------------|
| request | Request | Request original do fetch |
| verb | HttpVerb | Verbo HTTP detectado |
| key | EntityKey? | Presente em rotas /:key (ex: GET /users(1)) |
| query | Record<string,string> | Params da query string |
| store | StoreAdapter<T> | Acesso direto ao store (list/getByKey/insert/...) |
| reply | ReplyHelpers | Atalhos: json, ok, created, noContent, notFound, unauthorized, forbidden, badRequest |
| next() | () => Promise<Response> | Delega ao handler default da lib |
🚫 Desabilitando verbos
mockOData('/api/users', users).readonly().handlers() // apenas GET
mockOData('/api/users', users).disable('DELETE', 'PUT').handlers() // sem delete/putVerbos desabilitados retornam 405 Method Not Allowed.
🧩 Múltiplas collections — composeHandlers
import { mockOData, composeHandlers, authBearer } from '@cosmospro/msw-odata'
const auth = authBearer('secret')
export const handlers = composeHandlers(
mockOData('/api/users', users).withAuth(auth),
mockOData('/api/orders', orders).withAuth(auth),
mockOData('/api/products', products).readonly(),
)composeHandlers aceita builders, handlers MSW crus (http.get(...)) ou arrays deles — tudo é achatado em um único array pronto para setupWorker/setupServer.
🧪 Reset do store em testes
A lib clona o seed (via structuredClone) e trabalha numa cópia interna — seu array original nunca é mutado. Para voltar ao estado inicial entre testes:
const mock = mockOData('/api/users', users)
const server = setupServer(...mock.handlers())
beforeEach(() => {
mock.reset() // volta ao clone do seed
})⚙️ Opções
mockOData('/api/users', users, {
key: 'userId', // campo de chave primária (default: 'id')
version: 'v2', // 'v4' (default) ou 'v2'
alwaysIncludeCount: true, // força $count=true em GET list (default: false)
})Envelope OData v4 (default)
{
"@odata.context": "$metadata#users",
"@odata.count": 2,
"value": [ ... ]
}Envelope OData v2 — version: 'v2'
{
"d": {
"results": [ ... ],
"__count": "2"
}
}📋 Operadores OData suportados no $filter
| Operador / Função | Exemplo | Descrição |
|-------------------|------------------------------------------------|----------------------------------|
| eq | status eq 'active' | Igualdade (case-insensitive) |
| contains | contains(name,'João') | Contém substring |
| startswith | startswith(name,'Jo') | Começa com |
| endswith | endswith(email,'.com') | Termina com |
| and | contains(name,'Jo') and status eq 'active' | Conjunção lógica |
| or | status eq 'active' or status eq 'pending' | Disjunção lógica |
| not | not status eq 'inactive' | Negação lógica |
Campos aninhados via notação de ponto: customer.name eq 'João'.
Demais system query options suportadas: $orderby, $top, $skip, $select, $count.
🏗️ Estrutura do pacote
src/
├── index.ts # barrel público
├── types.ts # interfaces públicas
├── odataUtils.ts # shim de backwards-compat (re-exporta odata/engine)
├── odata/ # engine puro (sem dependência de MSW)
│ ├── engine.ts # applyODataQuery, createODataHandlerResponse
│ ├── parser.ts # parsing das query strings OData
│ ├── filter.ts # avaliação do AST de $filter
│ ├── orderby.ts # $orderby
│ └── select.ts # $select + helpers de campos aninhados
├── builder/ # fluent API sobre MSW
│ ├── mockOData.ts # builder principal
│ ├── defaultHandlers.ts # implementações default dos 7 verbos
│ ├── context.ts # HandlerContext + ReplyHelpers
│ ├── envelope.ts # envelopes OData v4 e v2
│ ├── keyParser.ts # parse de /Entity(1), /Entity('abc'), /Entity(k=1,k2=2)
│ └── compose.ts # composeHandlers
├── auth/
│ ├── bearer.ts # authBearer
│ └── guard.ts # middleware interno, 401 automático
├── store/
│ └── InMemoryStore.ts # store com Map + structuredClone + reset
└── tests/ # suíte de testes (Vitest)🧱 Low-level API (legada, ainda suportada)
Se você já tem handlers MSW próprios e só quer o motor de processamento OData, as duas funções originais continuam exportadas e funcionam igual à v1.0:
applyODataQuery<T>(queryString, collection)
import { applyODataQuery } from '@cosmospro/msw-odata'
const result = applyODataQuery(
"?$filter=status eq 'active'&$top=1&$count=true",
collection,
)
// result.value → dados filtrados
// result.count → total pré-paginação (quando $count=true)
// result.hasNext → indica próxima páginacreateODataHandlerResponse<T>(queryString, collection, includeCount?)
Wrapper sobre applyODataQuery que força $count=true por padrão e retorna { data, total, hasNext } — útil se você mantém um formato de envelope customizado:
import { http, HttpResponse } from 'msw'
import { createODataHandlerResponse } from '@cosmospro/msw-odata'
http.get('/api/users', ({ request }) => {
const queryString = new URL(request.url).search
const { data, total, hasNext } = createODataHandlerResponse(queryString, users)
return HttpResponse.json({ '@odata.count': total, value: data, hasNext })
})🛠️ Desenvolvimento
| Comando | Descrição |
|----------------------|------------------------------------------------|
| npm run build | Compila para CJS e ESM com tipagem (tsup) |
| npm run dev | Modo watch |
| npm run typecheck | Verificação de tipos sem emitir arquivos |
| npm run test | Executa todos os testes (Vitest) |
| npm run test:watch | Testes em modo watch |
| npm run test:ui | Abre a UI do Vitest em http://localhost:51204/__vitest__/ |
📄 Licença
MIT — Parte do projeto CosmosPro.
