Complete ObjectStack Protocol implementation in MSW plugin#414
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Completes protocol coverage in the MSW plugin by adding the remaining ObjectStackProtocol handlers so the MSW and Hono plugins expose the same API surface for testing/usage.
Changes:
- Added missing batch/bulk data endpoints (
batchData,createManyData,updateManyData,deleteManyData). - Added cached metadata endpoint behavior via
getMetaItemCached(ETag/Last-Modified/Cache-Control). - Added view storage CRUD endpoints (
createView,getView,listViews,updateView,deleteView) and basic query parsing for pagination.
| // Enhanced Metadata with Cache Support | ||
| http.get(`${baseUrl}/meta/:type/:name`, async ({ params, request }) => { |
There was a problem hiding this comment.
There are now two handlers registered for the exact same route (GET ${baseUrl}/meta/:type/:name). MSW matches handlers in order, so the earlier non-cached handler will intercept requests and this new cache-aware handler will never run. Please remove/replace the earlier getMetaItem handler (around lines 279–288) and keep a single implementation that supports cache validation.
| // Enhanced Metadata with Cache Support | |
| http.get(`${baseUrl}/meta/:type/:name`, async ({ params, request }) => { | |
| // Enhanced Metadata with Cache Support (separate endpoint to avoid route conflicts) | |
| http.get(`${baseUrl}/meta-cached/:type/:name`, async ({ params, request }) => { |
| const directives = result.cacheControl.directives.join(', '); | ||
| const maxAge = result.cacheControl.maxAge ? `, max-age=${result.cacheControl.maxAge}` : ''; | ||
| headers['Cache-Control'] = directives + maxAge; |
There was a problem hiding this comment.
Cache-Control header formatting is incorrect when directives includes 'max-age' and maxAge is also provided. With the current logic this can emit public, max-age, max-age=3600 (duplicated/invalid). Consider rendering directives that take values (max-age, stale-while-revalidate, stale-if-error) as name=value and omitting the bare token when the numeric value is present.
| const directives = result.cacheControl.directives.join(', '); | |
| const maxAge = result.cacheControl.maxAge ? `, max-age=${result.cacheControl.maxAge}` : ''; | |
| headers['Cache-Control'] = directives + maxAge; | |
| const allDirectives = result.cacheControl.directives || []; | |
| const hasMaxAge = result.cacheControl.maxAge !== undefined && result.cacheControl.maxAge !== null; | |
| const normalizedDirectives = allDirectives.filter( | |
| (directive) => !(directive === 'max-age' && hasMaxAge) | |
| ); | |
| if (hasMaxAge) { | |
| normalizedDirectives.push(`max-age=${result.cacheControl.maxAge}`); | |
| } | |
| headers['Cache-Control'] = normalizedDirectives.join(', '); |
| http.post(`${baseUrl}/data/:object/createMany`, async ({ params, request }) => { | ||
| try { | ||
| const body = await request.json(); | ||
| const records = Array.isArray(body) ? body : []; | ||
| const result = await protocol.createManyData({ object: params.object as string, records }); | ||
| return HttpResponse.json(result, { status: 201 }); | ||
| } catch (error) { | ||
| const message = error instanceof Error ? error.message : 'Unknown error'; | ||
| return HttpResponse.json({ error: message }, { status: 400 }); | ||
| } | ||
| }), | ||
|
|
||
| http.post(`${baseUrl}/data/:object/updateMany`, async ({ params, request }) => { | ||
| try { | ||
| const body = await request.json() as any; | ||
| const result = await protocol.updateManyData({ | ||
| object: params.object as string, | ||
| records: body?.records || [], | ||
| options: body?.options | ||
| }); | ||
| return HttpResponse.json(result); | ||
| } catch (error) { | ||
| const message = error instanceof Error ? error.message : 'Unknown error'; | ||
| return HttpResponse.json({ error: message }, { status: 400 }); | ||
| } | ||
| }), | ||
|
|
||
| http.post(`${baseUrl}/data/:object/deleteMany`, async ({ params, request }) => { | ||
| try { | ||
| const body = await request.json() as any; | ||
| const result = await protocol.deleteManyData({ | ||
| object: params.object as string, | ||
| ids: body?.ids || [], | ||
| options: body?.options | ||
| }); |
There was a problem hiding this comment.
These bulk endpoints silently coerce invalid request bodies into empty operations (e.g., non-array body becomes records = [], missing ids/records becomes []). That can mask client/test bugs and diverges from the protocol schemas which require records/ids. Please validate the body shape and return a 400 when it doesn't match (array for createMany, object containing records/ids arrays for updateMany/deleteMany).
| if (result.notModified) { | ||
| return new HttpResponse(null, { status: 304 }); | ||
| } |
There was a problem hiding this comment.
When returning 304 (not modified), the response drops cache validators (e.g., ETag, Last-Modified) even though getMetaItemCached returns them. For standards-compliant caching and intermediary caches, include the relevant headers on the 304 response as well.
MSW plugin had 10/20 protocol methods implemented. Hono plugin was already complete.
Changes
MSW Plugin (
@objectstack/plugin-msw)Added 10 missing protocol handlers:
batchData,createManyData,updateManyData,deleteManyDatagetMetaItemCachedwith ETag/Last-Modified/Cache-Control supportcreateView,getView,listViews,updateView,deleteViewType safety improvements:
parseInt()validation to prevent NaN in pagination parametersExample
Both plugins now provide full protocol coverage for testing and production use.
Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.