Skip to content

Commit 909ae8f

Browse files
Merge main into release
2 parents 4e6a5c6 + 3418ef8 commit 909ae8f

File tree

14 files changed

+369
-474
lines changed

14 files changed

+369
-474
lines changed

Diff for: .changeset/slimy-chicken-mix.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@firebase/firestore': patch
3+
'firebase': patch
4+
---
5+
6+
Reverted a change to use UTF-8 encoding in string comparisons which caused a performance issue. See [GitHub issue #8778](https://github.com/firebase/firebase-js-sdk/issues/8778)

Diff for: .github/CODEOWNERS

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ packages/app-check-types @hsubox76 @firebase/jssdk-global-approvers
7272
packages/app-check-interop-types @hsubox76 @firebase/jssdk-global-approvers
7373

7474
# Documentation Changes
75-
packages/firebase/index.d.ts @egilmorez @firebase/jssdk-global-approvers
75+
packages/firebase/compat/index.d.ts @egilmorez @firebase/jssdk-global-approvers
7676
scripts/docgen/content-sources/ @egilmorez @firebase/jssdk-global-approvers
7777
docs-devsite/ @firebase/firebase-techwriters
7878

Diff for: .gitignore

+4-1
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,7 @@ docs/
100100

101101
# vertexai test data
102102
vertexai-sdk-test-data
103-
mocks-lookup.ts
103+
mocks-lookup.ts
104+
105+
# temp changeset output
106+
changeset-temp.json

Diff for: CONTRIBUTING.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ Reference docs for the Firebase [JS SDK](https://firebase.google.com/docs/refere
206206
[Typedoc](https://typedoc.org/).
207207
208208
Typedoc generates this documentation from the main
209-
[firebase index.d.ts type definition file](packages/firebase/index.d.ts). Any updates to
209+
[firebase index.d.ts type definition file](packages/firebase/compat/index.d.ts). Any updates to
210210
documentation should be made in that file.
211211
212212
If any pages are added or removed by your change (by adding or removing a class or interface), the

Diff for: e2e/fix-jsdom-environment.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,21 @@
1818
import JSDOMEnvironment from 'jest-environment-jsdom';
1919

2020
/**
21-
* JSDOMEnvironment patch to polyfill missing fetch with native
22-
* Node fetch
21+
* JSDOMEnvironment patch to polyfill missing APIs with Node APIs.
2322
*/
2423
// https://github.com/facebook/jest/blob/v29.4.3/website/versioned_docs/version-29.4/Configuration.md#testenvironment-string
2524
export default class FixJSDOMEnvironment extends JSDOMEnvironment {
2625
constructor(...args: ConstructorParameters<typeof JSDOMEnvironment>) {
2726
super(...args);
2827

29-
// FIXME https://github.com/jsdom/jsdom/issues/1724
28+
// Fetch
29+
// FIXME: https://github.com/jsdom/jsdom/issues/1724
3030
this.global.fetch = fetch;
3131
this.global.Headers = Headers;
3232
this.global.Request = Request;
3333
this.global.Response = Response;
34+
35+
// Util
36+
this.global.TextEncoder = TextEncoder;
3437
}
3538
}

Diff for: e2e/package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
"description": "",
55
"main": "index.js",
66
"scripts": {
7-
"setup": "node test-setup.js",
87
"test": "yarn jest",
98
"test:compat": "yarn jest tests/compat.test.ts",
109
"test:modular": "yarn jest tests/modular.test.ts",
@@ -18,7 +17,7 @@
1817
"author": "",
1918
"license": "ISC",
2019
"dependencies": {
21-
"firebase": "11.0.2"
20+
"firebase": "11.3.0"
2221
},
2322
"devDependencies": {
2423
"@babel/core": "7.26.0",

Diff for: e2e/yarn.lock

+235-229
Large diffs are not rendered by default.

Diff for: packages/firestore/src/local/indexeddb_remote_document_cache.ts

-4
Original file line numberDiff line numberDiff line change
@@ -655,9 +655,5 @@ export function dbKeyComparator(l: DocumentKey, r: DocumentKey): number {
655655
return cmp;
656656
}
657657

658-
// TODO(b/329441702): Document IDs should be sorted by UTF-8 encoded byte
659-
// order, but IndexedDB sorts strings lexicographically. Document ID
660-
// comparison here still relies on primitive comparison to avoid mismatches
661-
// observed in snapshot listeners with Unicode characters in documentIds
662658
return primitiveComparator(left[left.length - 1], right[right.length - 1]);
663659
}

Diff for: packages/firestore/src/model/path.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { Integer } from '@firebase/webchannel-wrapper/bloom-blob';
1919

2020
import { debugAssert, fail } from '../util/assert';
2121
import { Code, FirestoreError } from '../util/error';
22-
import { primitiveComparator, compareUtf8Strings } from '../util/misc';
2322

2423
export const DOCUMENT_KEY_NAME = '__name__';
2524

@@ -182,7 +181,7 @@ abstract class BasePath<B extends BasePath<B>> {
182181
return comparison;
183182
}
184183
}
185-
return primitiveComparator(p1.length, p2.length);
184+
return Math.sign(p1.length - p2.length);
186185
}
187186

188187
private static compareSegments(lhs: string, rhs: string): number {
@@ -202,7 +201,13 @@ abstract class BasePath<B extends BasePath<B>> {
202201
);
203202
} else {
204203
// both non-numeric
205-
return compareUtf8Strings(lhs, rhs);
204+
if (lhs < rhs) {
205+
return -1;
206+
}
207+
if (lhs > rhs) {
208+
return 1;
209+
}
210+
return 0;
206211
}
207212
}
208213

Diff for: packages/firestore/src/model/values.ts

+3-7
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,7 @@ import {
2525
Value
2626
} from '../protos/firestore_proto_api';
2727
import { fail } from '../util/assert';
28-
import {
29-
arrayEquals,
30-
primitiveComparator,
31-
compareUtf8Strings
32-
} from '../util/misc';
28+
import { arrayEquals, primitiveComparator } from '../util/misc';
3329
import { forEach, objectSize } from '../util/obj';
3430
import { isNegativeZero } from '../util/types';
3531

@@ -255,7 +251,7 @@ export function valueCompare(left: Value, right: Value): number {
255251
getLocalWriteTime(right)
256252
);
257253
case TypeOrder.StringValue:
258-
return compareUtf8Strings(left.stringValue!, right.stringValue!);
254+
return primitiveComparator(left.stringValue!, right.stringValue!);
259255
case TypeOrder.BlobValue:
260256
return compareBlobs(left.bytesValue!, right.bytesValue!);
261257
case TypeOrder.RefValue:
@@ -404,7 +400,7 @@ function compareMaps(left: MapValue, right: MapValue): number {
404400
rightKeys.sort();
405401

406402
for (let i = 0; i < leftKeys.length && i < rightKeys.length; ++i) {
407-
const keyCompare = compareUtf8Strings(leftKeys[i], rightKeys[i]);
403+
const keyCompare = primitiveComparator(leftKeys[i], rightKeys[i]);
408404
if (keyCompare !== 0) {
409405
return keyCompare;
410406
}

Diff for: packages/firestore/src/util/misc.ts

-17
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
*/
1717

1818
import { randomBytes } from '../platform/random_bytes';
19-
import { newTextEncoder } from '../platform/text_serializer';
2019

2120
import { debugAssert } from './assert';
2221

@@ -75,22 +74,6 @@ export interface Equatable<T> {
7574
isEqual(other: T): boolean;
7675
}
7776

78-
/** Compare strings in UTF-8 encoded byte order */
79-
export function compareUtf8Strings(left: string, right: string): number {
80-
// Convert the string to UTF-8 encoded bytes
81-
const encodedLeft = newTextEncoder().encode(left);
82-
const encodedRight = newTextEncoder().encode(right);
83-
84-
for (let i = 0; i < Math.min(encodedLeft.length, encodedRight.length); i++) {
85-
const comparison = primitiveComparator(encodedLeft[i], encodedRight[i]);
86-
if (comparison !== 0) {
87-
return comparison;
88-
}
89-
}
90-
91-
return primitiveComparator(encodedLeft.length, encodedRight.length);
92-
}
93-
9477
export interface Iterable<V> {
9578
forEach: (cb: (v: V) => void) => void;
9679
}

Diff for: packages/firestore/test/integration/api/database.test.ts

-195
Original file line numberDiff line numberDiff line change
@@ -2424,199 +2424,4 @@ apiDescribe('Database', persistence => {
24242424
});
24252425
});
24262426
});
2427-
2428-
describe('Sort unicode strings', () => {
2429-
const expectedDocs = ['b', 'a', 'c', 'f', 'e', 'd', 'g'];
2430-
it('snapshot listener sorts unicode strings the same as server', async () => {
2431-
const testDocs = {
2432-
'a': { value: 'Łukasiewicz' },
2433-
'b': { value: 'Sierpiński' },
2434-
'c': { value: '岩澤' },
2435-
'd': { value: '🄟' },
2436-
'e': { value: 'P' },
2437-
'f': { value: '︒' },
2438-
'g': { value: '🐵' }
2439-
};
2440-
2441-
return withTestCollection(persistence, testDocs, async collectionRef => {
2442-
const orderedQuery = query(collectionRef, orderBy('value'));
2443-
2444-
const getSnapshot = await getDocsFromServer(orderedQuery);
2445-
expect(toIds(getSnapshot)).to.deep.equal(expectedDocs);
2446-
2447-
const storeEvent = new EventsAccumulator<QuerySnapshot>();
2448-
const unsubscribe = onSnapshot(orderedQuery, storeEvent.storeEvent);
2449-
const watchSnapshot = await storeEvent.awaitEvent();
2450-
expect(toIds(watchSnapshot)).to.deep.equal(toIds(getSnapshot));
2451-
2452-
unsubscribe();
2453-
2454-
await checkOnlineAndOfflineResultsMatch(orderedQuery, ...expectedDocs);
2455-
});
2456-
});
2457-
2458-
it('snapshot listener sorts unicode strings in array the same as server', async () => {
2459-
const testDocs = {
2460-
'a': { value: ['Łukasiewicz'] },
2461-
'b': { value: ['Sierpiński'] },
2462-
'c': { value: ['岩澤'] },
2463-
'd': { value: ['🄟'] },
2464-
'e': { value: ['P'] },
2465-
'f': { value: ['︒'] },
2466-
'g': { value: ['🐵'] }
2467-
};
2468-
2469-
return withTestCollection(persistence, testDocs, async collectionRef => {
2470-
const orderedQuery = query(collectionRef, orderBy('value'));
2471-
2472-
const getSnapshot = await getDocsFromServer(orderedQuery);
2473-
expect(toIds(getSnapshot)).to.deep.equal(expectedDocs);
2474-
2475-
const storeEvent = new EventsAccumulator<QuerySnapshot>();
2476-
const unsubscribe = onSnapshot(orderedQuery, storeEvent.storeEvent);
2477-
const watchSnapshot = await storeEvent.awaitEvent();
2478-
expect(toIds(watchSnapshot)).to.deep.equal(toIds(getSnapshot));
2479-
2480-
unsubscribe();
2481-
2482-
await checkOnlineAndOfflineResultsMatch(orderedQuery, ...expectedDocs);
2483-
});
2484-
});
2485-
2486-
it('snapshot listener sorts unicode strings in map the same as server', async () => {
2487-
const testDocs = {
2488-
'a': { value: { foo: 'Łukasiewicz' } },
2489-
'b': { value: { foo: 'Sierpiński' } },
2490-
'c': { value: { foo: '岩澤' } },
2491-
'd': { value: { foo: '🄟' } },
2492-
'e': { value: { foo: 'P' } },
2493-
'f': { value: { foo: '︒' } },
2494-
'g': { value: { foo: '🐵' } }
2495-
};
2496-
2497-
return withTestCollection(persistence, testDocs, async collectionRef => {
2498-
const orderedQuery = query(collectionRef, orderBy('value'));
2499-
2500-
const getSnapshot = await getDocsFromServer(orderedQuery);
2501-
expect(toIds(getSnapshot)).to.deep.equal(expectedDocs);
2502-
2503-
const storeEvent = new EventsAccumulator<QuerySnapshot>();
2504-
const unsubscribe = onSnapshot(orderedQuery, storeEvent.storeEvent);
2505-
const watchSnapshot = await storeEvent.awaitEvent();
2506-
expect(toIds(watchSnapshot)).to.deep.equal(toIds(getSnapshot));
2507-
2508-
unsubscribe();
2509-
2510-
await checkOnlineAndOfflineResultsMatch(orderedQuery, ...expectedDocs);
2511-
});
2512-
});
2513-
2514-
it('snapshot listener sorts unicode strings in map key the same as server', async () => {
2515-
const testDocs = {
2516-
'a': { value: { 'Łukasiewicz': true } },
2517-
'b': { value: { 'Sierpiński': true } },
2518-
'c': { value: { '岩澤': true } },
2519-
'd': { value: { '🄟': true } },
2520-
'e': { value: { 'P': true } },
2521-
'f': { value: { '︒': true } },
2522-
'g': { value: { '🐵': true } }
2523-
};
2524-
2525-
return withTestCollection(persistence, testDocs, async collectionRef => {
2526-
const orderedQuery = query(collectionRef, orderBy('value'));
2527-
2528-
const getSnapshot = await getDocsFromServer(orderedQuery);
2529-
expect(toIds(getSnapshot)).to.deep.equal(expectedDocs);
2530-
2531-
const storeEvent = new EventsAccumulator<QuerySnapshot>();
2532-
const unsubscribe = onSnapshot(orderedQuery, storeEvent.storeEvent);
2533-
const watchSnapshot = await storeEvent.awaitEvent();
2534-
expect(toIds(watchSnapshot)).to.deep.equal(toIds(getSnapshot));
2535-
2536-
unsubscribe();
2537-
2538-
await checkOnlineAndOfflineResultsMatch(orderedQuery, ...expectedDocs);
2539-
});
2540-
});
2541-
2542-
it('snapshot listener sorts unicode strings in document key the same as server', async () => {
2543-
const testDocs = {
2544-
'Łukasiewicz': { value: true },
2545-
'Sierpiński': { value: true },
2546-
'岩澤': { value: true },
2547-
'🄟': { value: true },
2548-
'P': { value: true },
2549-
'︒': { value: true },
2550-
'🐵': { value: true }
2551-
};
2552-
2553-
return withTestCollection(persistence, testDocs, async collectionRef => {
2554-
const orderedQuery = query(collectionRef, orderBy(documentId()));
2555-
2556-
const getSnapshot = await getDocsFromServer(orderedQuery);
2557-
const expectedDocs = [
2558-
'Sierpiński',
2559-
'Łukasiewicz',
2560-
'岩澤',
2561-
'︒',
2562-
'P',
2563-
'🄟',
2564-
'🐵'
2565-
];
2566-
expect(toIds(getSnapshot)).to.deep.equal(expectedDocs);
2567-
2568-
const storeEvent = new EventsAccumulator<QuerySnapshot>();
2569-
const unsubscribe = onSnapshot(orderedQuery, storeEvent.storeEvent);
2570-
const watchSnapshot = await storeEvent.awaitEvent();
2571-
expect(toIds(watchSnapshot)).to.deep.equal(toIds(getSnapshot));
2572-
2573-
unsubscribe();
2574-
2575-
await checkOnlineAndOfflineResultsMatch(orderedQuery, ...expectedDocs);
2576-
});
2577-
});
2578-
2579-
// eslint-disable-next-line no-restricted-properties
2580-
(persistence.storage === 'indexeddb' ? it.skip : it)(
2581-
'snapshot listener sorts unicode strings in document key the same as server with persistence',
2582-
async () => {
2583-
const testDocs = {
2584-
'Łukasiewicz': { value: true },
2585-
'Sierpiński': { value: true },
2586-
'岩澤': { value: true },
2587-
'🄟': { value: true },
2588-
'P': { value: true },
2589-
'︒': { value: true },
2590-
'🐵': { value: true }
2591-
};
2592-
2593-
return withTestCollection(
2594-
persistence,
2595-
testDocs,
2596-
async collectionRef => {
2597-
const orderedQuery = query(collectionRef, orderBy('value'));
2598-
2599-
const getSnapshot = await getDocsFromServer(orderedQuery);
2600-
expect(toIds(getSnapshot)).to.deep.equal([
2601-
'Sierpiński',
2602-
'Łukasiewicz',
2603-
'岩澤',
2604-
'︒',
2605-
'P',
2606-
'🄟',
2607-
'🐵'
2608-
]);
2609-
2610-
const storeEvent = new EventsAccumulator<QuerySnapshot>();
2611-
const unsubscribe = onSnapshot(orderedQuery, storeEvent.storeEvent);
2612-
const watchSnapshot = await storeEvent.awaitEvent();
2613-
// TODO: IndexedDB sorts string lexicographically, and misses the document with ID '🄟','🐵'
2614-
expect(toIds(watchSnapshot)).to.deep.equal(toIds(getSnapshot));
2615-
2616-
unsubscribe();
2617-
}
2618-
);
2619-
}
2620-
);
2621-
});
26222427
});

Diff for: packages/remote-config/src/remote_config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const DEFAULT_CACHE_MAX_AGE_MILLIS = 12 * 60 * 60 * 1000; // Twelve hours.
3232
/**
3333
* Encapsulates business logic mapping network and storage dependencies to the public SDK API.
3434
*
35-
* See {@link https://github.com/firebase/firebase-js-sdk/blob/main/packages/firebase/index.d.ts|interface documentation} for method descriptions.
35+
* See {@link https://github.com/firebase/firebase-js-sdk/blob/main/packages/firebase/compat/index.d.ts|interface documentation} for method descriptions.
3636
*/
3737
export class RemoteConfig implements RemoteConfigType {
3838
/**

0 commit comments

Comments
 (0)