diff --git a/packages/firebase/firestore/pipelines/index.cdn.ts b/packages/firebase/firestore/pipelines/index.cdn.ts new file mode 100644 index 00000000000..81e81b39d81 --- /dev/null +++ b/packages/firebase/firestore/pipelines/index.cdn.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from '@firebase/firestore'; + +import * as pipelines from '@firebase/firestore/pipelines'; +export { pipelines }; diff --git a/packages/firebase/gulpfile.js b/packages/firebase/gulpfile.js index 4ea24182af2..e05345fbdf7 100644 --- a/packages/firebase/gulpfile.js +++ b/packages/firebase/gulpfile.js @@ -21,7 +21,7 @@ const replace = require('gulp-replace'); const pkgJson = require('./package.json'); const files = pkgJson.components.map(component => { - const componentName = component.replace('/', '-'); + const componentName = component.replaceAll('/', '-'); return `firebase-${componentName}.js`; }); const FIREBASE_APP_URL = `https://www.gstatic.com/firebasejs/${pkgJson.version}/firebase-app.js`; diff --git a/packages/firebase/package.json b/packages/firebase/package.json index 704b5395a40..cab2a72f634 100644 --- a/packages/firebase/package.json +++ b/packages/firebase/package.json @@ -476,8 +476,8 @@ "auth/web-extension", "functions", "firestore", - "firestore/pipelines", "firestore/lite", + "firestore/pipelines", "firestore/lite/pipelines", "installations", "storage", diff --git a/packages/firebase/rollup.config.js b/packages/firebase/rollup.config.js index f96ff01666c..87b9f7c834d 100644 --- a/packages/firebase/rollup.config.js +++ b/packages/firebase/rollup.config.js @@ -20,6 +20,7 @@ import commonjs from '@rollup/plugin-commonjs'; import json from '@rollup/plugin-json'; import pkg from './package.json'; import { resolve } from 'path'; +import { existsSync } from 'fs'; import resolveModule from '@rollup/plugin-node-resolve'; import rollupTypescriptPlugin from 'rollup-plugin-typescript2'; import sourcemaps from 'rollup-plugin-sourcemaps'; @@ -149,10 +150,12 @@ const cdnBuilds = [ .map(component => { // It is needed for handling sub modules, for example firestore/lite which should produce firebase-firestore-lite.js // Otherwise, we will create a directory with '/' in the name. - const componentName = component.replace('/', '-'); + const componentName = component.replaceAll('/', '-'); return { - input: `${component}/index.ts`, + input: existsSync(`${component}/index.cdn.ts`) + ? `${component}/index.cdn.ts` + : `${component}/index.ts`, output: { file: `firebase-${componentName}.js`, sourcemap: true, diff --git a/packages/firestore/rollup.config.js b/packages/firestore/rollup.config.js index c9222f69ab4..b9db3922098 100644 --- a/packages/firestore/rollup.config.js +++ b/packages/firestore/rollup.config.js @@ -59,6 +59,35 @@ const browserPlugins = [ terser(util.manglePrivatePropertiesOptions) ]; +// TODO - update the implementation to match all content in the declare module block. +function declareModuleReplacePlugin() { + // The regex we created earlier + const moduleToReplace = + /declare module '\.\/\S+' \{\s+interface Firestore \{\s+pipeline\(\): PipelineSource;\s+}\s*}/gm; + + // What to replace it with (an empty string to remove it) + const replacement = + 'interface Firestore {pipeline(): PipelineSource;}'; + + return { + name: 'declare-module-replace', + generateBundle(options, bundle) { + const outputFileName = 'global_index.d.ts'; + if (!bundle[outputFileName]) { + console.warn( + `[regexReplacePlugin] File not found in bundle: ${outputFileName}` + ); + return; + } + + const chunk = bundle[outputFileName]; + if (chunk.type === 'chunk') { + chunk.code = chunk.code.replace(moduleToReplace, replacement); + } + } + }; +} + const allBuilds = [ // Intermediate Node ESM build without build target reporting // this is an intermediate build used to generate the actual esm and cjs builds @@ -214,7 +243,7 @@ const allBuilds = [ } }, { - input: 'dist/firestore/src/index.d.ts', + input: 'dist/firestore/src/global.d.ts', output: { file: 'dist/firestore/src/global_index.d.ts', format: 'es' @@ -222,7 +251,9 @@ const allBuilds = [ plugins: [ dts({ respectExternal: true - }) + }), + + declareModuleReplacePlugin() ] } ]; diff --git a/packages/firestore/src/global.ts b/packages/firestore/src/global.ts new file mode 100644 index 00000000000..529f43e022b --- /dev/null +++ b/packages/firestore/src/global.ts @@ -0,0 +1,23 @@ +/** + * @license + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// This file supports a special internal build that includes the entire +// Firestore classic and pipeline api surface in one bundle. + +import * as pipelines from './api_pipelines'; +export * from './api'; +export { pipelines }; diff --git a/packages/firestore/src/remote/internal_serializer.ts b/packages/firestore/src/remote/internal_serializer.ts index 29a68620efc..1e759dab814 100644 --- a/packages/firestore/src/remote/internal_serializer.ts +++ b/packages/firestore/src/remote/internal_serializer.ts @@ -18,14 +18,23 @@ import { ensureFirestoreConfigured, Firestore } from '../api/database'; import { AggregateImpl } from '../core/aggregate'; import { queryToAggregateTarget, queryToTarget } from '../core/query'; +import { + StructuredPipeline, + StructuredPipelineOptions +} from '../core/structured_pipeline'; import { AggregateSpec } from '../lite-api/aggregate_types'; import { getDatastore } from '../lite-api/components'; import { Pipeline } from '../lite-api/pipeline'; import { Query } from '../lite-api/reference'; +import { ExecutePipelineRequest as ProtoExecutePipelineRequest } from '../protos/firestore_proto_api'; import { cast } from '../util/input_validation'; import { mapToArray } from '../util/obj'; -import { toQueryTarget, toRunAggregationQueryRequest } from './serializer'; +import { + getEncodedDatabaseId, + toQueryTarget, + toRunAggregationQueryRequest +} from './serializer'; /** * @internal @@ -112,5 +121,15 @@ export function _internalPipelineToExecutePipelineRequestProto( if (serializer === undefined) { return null; } - return pipeline._toProto(serializer); + + const structuredPipeline = new StructuredPipeline( + pipeline, + new StructuredPipelineOptions() + ); + const executePipelineRequest: ProtoExecutePipelineRequest = { + database: getEncodedDatabaseId(serializer), + structuredPipeline: structuredPipeline._toProto(serializer) + }; + + return executePipelineRequest; } diff --git a/packages/firestore/test/integration/api/pipeline.test.ts b/packages/firestore/test/integration/api/pipeline.test.ts index aab69008254..9ebf9ced4b5 100644 --- a/packages/firestore/test/integration/api/pipeline.test.ts +++ b/packages/firestore/test/integration/api/pipeline.test.ts @@ -346,18 +346,19 @@ const timestampDeltaMS = 1000; describe('console support', () => { it('supports internal serialization to proto', async () => { + // Perform the same test as the console const pipeline = firestore .pipeline() - .collection('books') - .where(equal('awards.hugo', true)) - .select( - 'title', - field('nestedField.level.1'), - mapGet('nestedField', 'level.1').mapGet('level.2').as('nested') - ); + .collection('customers') + .where(field('country').equal('United Kingdom')); const proto = _internalPipelineToExecutePipelineRequestProto(pipeline); - expect(proto).not.to.be.null; + + const expectedStructuredPipelineProto = + '{"pipeline":{"stages":[{"name":"collection","options":{},"args":[{"referenceValue":"/customers"}]},{"name":"where","options":{},"args":[{"functionValue":{"name":"equal","args":[{"fieldReferenceValue":"country"},{"stringValue":"United Kingdom"}]}}]}]}}'; + expect(JSON.stringify(proto.structuredPipeline)).to.equal( + expectedStructuredPipelineProto + ); }); }); diff --git a/packages/firestore/test/unit/remote/grpc_connection.test.ts b/packages/firestore/test/unit/remote/grpc_connection.node.test.ts similarity index 100% rename from packages/firestore/test/unit/remote/grpc_connection.test.ts rename to packages/firestore/test/unit/remote/grpc_connection.node.test.ts diff --git a/scripts/size_report/report_binary_size.ts b/scripts/size_report/report_binary_size.ts index da1ad166702..34dce01924e 100644 --- a/scripts/size_report/report_binary_size.ts +++ b/scripts/size_report/report_binary_size.ts @@ -57,7 +57,7 @@ function generateReportForCDNScripts(): Report[] { ...special_files.map((file: string) => `${firebaseRoot}/${file}`), ...pkgJson.components.map( (component: string) => - `${firebaseRoot}/firebase-${component.replace('/', '-')}.js` + `${firebaseRoot}/firebase-${component.replaceAll('/', '-')}.js` ), ...compatPkgJson.components.map( (component: string) => `${firebaseRoot}/firebase-${component}-compat.js`