ws adapter: match main truth (id injection + setDoc merge)
- getDoc/getCollection/subscribe inject {id,...data} (main firebase shape)
- getCollection normalizes backend {id,data}[] OR bare data[] → {id,...data}
- setDoc(opts.merge) → PATCH (create-on-miss); else PUT (replace)
- ws-reconnect test asserts id now present
24/24 server green. All suites green: App 83, shared 91, server 24.
This commit is contained in:
@@ -48,7 +48,7 @@ describe('BUG-8: ws adapter reconnect after drop', () => {
|
||||
await flush(1000);
|
||||
|
||||
const last = calls[calls.length - 1];
|
||||
expect(last).toEqual({ name: 'V2' });
|
||||
expect(last).toEqual({ id: 'a', name: 'V2' });
|
||||
} finally {
|
||||
await new Promise(r => storage.dispose(r));
|
||||
}
|
||||
|
||||
+29
-4
@@ -33,6 +33,15 @@ function createWsStorage({ baseUrl, wsUrl } = {}) {
|
||||
return p.replace(/^[\s\S]*\/public\/data\//, '');
|
||||
}
|
||||
|
||||
// Inject id (last path segment) into doc — matches main firebase truth:
|
||||
// { id: docSnap.id, ...snap.data() }
|
||||
// Backend stores docs WITHOUT id; client adds it so all adapters share shape.
|
||||
function withId(rawPath, data) {
|
||||
if (data === null || data === undefined) return data;
|
||||
const id = norm(rawPath).split('/').pop();
|
||||
return { id, ...data };
|
||||
}
|
||||
|
||||
const docSubs = new Map(); // path -> Set<cb>
|
||||
const collSubs = new Map(); // collPath -> Set<cb>
|
||||
let ws = null;
|
||||
@@ -154,12 +163,19 @@ function createWsStorage({ baseUrl, wsUrl } = {}) {
|
||||
async getDoc(rawPath) {
|
||||
const p = norm(rawPath);
|
||||
const res = await api('GET', '/api/doc', { path: p });
|
||||
return res && res.data !== undefined ? res.data : null;
|
||||
const data = res && res.data !== undefined ? res.data : null;
|
||||
return withId(rawPath, data);
|
||||
},
|
||||
|
||||
async setDoc(rawPath, data) {
|
||||
async setDoc(rawPath, data, opts = {}) {
|
||||
const p = norm(rawPath);
|
||||
// merge flag -> PATCH (shallow merge, create-on-miss). Matches firebase
|
||||
// setDoc(path, data, {merge:true}) semantics.
|
||||
if (opts.merge) {
|
||||
await api('PATCH', '/api/doc', null, { path: p, patch: data });
|
||||
} else {
|
||||
await api('PUT', '/api/doc', null, { path: p, data });
|
||||
}
|
||||
},
|
||||
|
||||
async updateDoc(rawPath, patch) {
|
||||
@@ -180,7 +196,16 @@ function createWsStorage({ baseUrl, wsUrl } = {}) {
|
||||
|
||||
async getCollection(rawCollPath) {
|
||||
const p = norm(rawCollPath);
|
||||
return await api('GET', '/api/collection', { path: p });
|
||||
const docs = await api('GET', '/api/collection', { path: p });
|
||||
// Backend returns array of { id, data } OR bare data[]; normalize to
|
||||
// { id, ...data } (firebase truth).
|
||||
if (!Array.isArray(docs)) return [];
|
||||
return docs.map(d => {
|
||||
if (d && typeof d === 'object' && 'id' in d && 'data' in d) {
|
||||
return { id: d.id, ...d.data };
|
||||
}
|
||||
return { ...d };
|
||||
});
|
||||
},
|
||||
|
||||
async batchWrite(ops) {
|
||||
@@ -190,7 +215,7 @@ function createWsStorage({ baseUrl, wsUrl } = {}) {
|
||||
|
||||
subscribeDoc(rawPath, cb, errCb) {
|
||||
const p = norm(rawPath);
|
||||
// Initial value via REST (independent of WS connect).
|
||||
// Initial value via REST (independent of WS connect). getDoc injects id.
|
||||
storage.getDoc(p).then(cb).catch(() => {});
|
||||
// WS only for subsequent change notifications.
|
||||
ensureWs().then(() => {
|
||||
|
||||
Reference in New Issue
Block a user