Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions packages/realm-server/tests/atomic-endpoints-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -554,8 +554,19 @@ module(basename(import.meta.filename), function () {
},
],
};
// This module batch is setup for the instance batch below, which
// adopts from Place/Country. /_atomic returns as soon as writes are
// durable — not indexed — and a subsequent /_atomic write does not
// wait for a prior write's indexing to settle. Without waitForIndex
// the modules can still be indexing when the instance batch's
// fileSerialization looks up the Place definition; that lookup then
// races the in-flight module index (generation bump discards the
// on-demand prerender result) and the batch fails with
// FilterRefersToNonexistentTypeError. waitForIndex makes the modules
// indexed before we move on, which is what a caller writing modules
// then dependent instances across separate batches must do.
let response = await request
.post('/_atomic')
.post('/_atomic?waitForIndex=true')
.set('Accept', SupportedMimeType.JSONAPI)
.set(
'Authorization',
Expand Down Expand Up @@ -637,7 +648,13 @@ module(basename(import.meta.filename), function () {
`Bearer ${createJWT(testRealm, 'user', ['read', 'write'])}`,
)
.send(JSON.stringify(instanceDoc));
assert.strictEqual(instanceResponse.status, 201);
assert.strictEqual(
instanceResponse.status,
201,
`expected 201, got ${instanceResponse.status}: ${JSON.stringify(
instanceResponse.body,
)}`,
);
assert.strictEqual(instanceResponse.body['atomic:results'].length, 2);
});
test('can write new instance with new module', async function (assert) {
Expand Down
16 changes: 14 additions & 2 deletions packages/runtime-common/realm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2462,9 +2462,21 @@ export class Realm {
// Log the underlying exception before returning 500 —
// otherwise callers only see "Write Error" and the original
// stack trace is lost, making atomic-batch failures
// effectively undebuggable.
// effectively undebuggable. Include e.cause explicitly: errors
// like FilterRefersToNonexistentTypeError carry the actionable
// detail (which module/definition was missing, or that a
// concurrent invalidation discarded the lookup) in their cause,
// not their message, so without this the real reason is swallowed.
let cause =
e?.cause instanceof Error
? `${e.cause.message}\n${e.cause.stack ?? '(no stack)'}`
: e?.cause != null
? String(e.cause)
: undefined;
this.#log.error(
`Atomic write failed: ${e.message}\n${e.stack ?? '(no stack)'}`,
`Atomic write failed: ${e.message}${
cause ? `\ncause: ${cause}` : ''
}\n${e.stack ?? '(no stack)'}`,
);
return createResponse({
body: JSON.stringify({
Expand Down
Loading