-
Notifications
You must be signed in to change notification settings - Fork 410
Description
[READ] Step 1: Are you in the right place?
- For issues related to the code in this repository file a Github issue.
- If the issue pertains to Cloud Firestore, read the instructions in the "Firestore issue"
template. - For general technical questions, post a question on StackOverflow
with the firebase tag. - For general Firebase discussion, use the firebase-talk
google group. - For help troubleshooting your application that does not fall under one
of the above categories, reach out to the personalized
Firebase support channel.
[REQUIRED] Step 2: Describe your environment
- Operating System version: Window 11
- Firebase SDK version: 13.6.0
- Firebase Product: dataconnect
- Node.js version: 22.17.1
- NPM version: 10.9.2
[REQUIRED] Step 3: Describe the problem
Steps to reproduce:
- Use the minimal GraphQL schema with a text (
String) field. - Initialize the Firebase Admin app and get a Data Connect instance (optionally connect to the Data Connect emulator).
- Attempt to insert a row with a string field that has special newline characters, e.g.
\n,\retc.
Relevant Code:
Minimal schema (dataconnect/schema/schema.gql):
type House @table {
id: UUID! @default(expr: "uuidV4()")
address: String!
residents: String! # new line separated text value
}Minimal script to run an insert mutation:
import { getDataConnect } from "firebase-admin/data-connect";
import { initializeApp } from "firebase-admin/app";
initializeApp();
// For emulator testing:
// process.env.DATA_CONNECT_EMULATOR_HOST = "127.0.0.1:9399";
const dc = getDataConnect({
location: "us-west2", // or your location
serviceId: "your-service-id",
});
async function testInsert() {
try {
await dc.insert("House", {
address: "20/A Zahir Raihan Road, Dhaka-1000, Dhaka, Bangladesh",
residents: "Zul, 24\nMonim, 25\nRubai, 22"
});
console.log("Success");
} catch (error) {
console.error("Error:", error);
}
}
testInsert();Expected Behavior:
The code should not throw any errors and the data should be migrated to the database.
Actual Behavior:
It's throwing a weird error:
27 | * @param errorInfo - The error information (code and message).
28 | * @constructor
29 | * @internal
30 | */
31 | constructor(errorInfo) {
32 | super(errorInfo.message);
^
error: Unexpected <Invalid>. Make sure that your table name passed in matches the type name in your GraphQL schema file.
errorInfo: {
code: "data-connect/query-error",
message: "Unexpected <Invalid>. Make sure that your table name passed in matches the type name in your GraphQL schema file.",
},
codePrefix: "data-connect",
at new FirebaseError (E:\Programming\react\vote-camp\node_modules\firebase-admin\lib\utils\error.js:32:9)
After scouring through the source code, I have managed to locate the issue in src/data-connect/data-connect-api-client-internal.ts. The objectToString method (lines 414-454) is doing some partial string serialization which is failing to account for special characters like \n.
private objectToString(data: unknown): string {
if (typeof data === 'string') {
const escapedString = data
.replace(/\\/g, '\\\\') // Replace \ with \\
.replace(/"/g, '\\"'); // Replace " with \"
return `"${escapedString}"`;
}
if (typeof data === 'number' || typeof data === 'boolean' || data === null) {
return String(data);
}
if (validator.isArray(data)) {
const elements = data.map(item => this.objectToString(item)).join(', ');
return `[${elements}]`;
}
if (typeof data === 'object' && data !== null) {
// Filter out properties where the value is undefined BEFORE mapping
const kvPairs = Object.entries(data)
.filter(([, val]) => val !== undefined)
.map(([key, val]) => {
// GraphQL object keys are typically unquoted.
return `${key}: ${this.objectToString(val)}`;
});
if (kvPairs.length === 0) {
return '{}'; // Represent an object with no defined properties as {}
}
return `{ ${kvPairs.join(', ')} }`;
}
// If value is undefined (and not an object property, which is handled above,
// e.g., if objectToString(undefined) is called directly or for an array element)
// it should be represented as 'null'.
if (typeof data === 'undefined') {
return 'null';
}
// Fallback for any other types (e.g., Symbol, BigInt - though less common in GQL contexts)
// Consider how these should be handled or if an error should be thrown.
// For now, simple string conversion.
return String(data);
}In taking a closer look:
if (typeof data === 'string') {
const escapedString = data
.replace(/\\/g, '\\\\') // Replace \ with \\
.replace(/"/g, '\\"'); // Replace " with \"
return `"${escapedString}"`;
}This part of the code is only escaping \ and " but things like the newline character escaping is missing. The mutation operation string is getting split is two and is therefore incomplete, which is also the reason behind the Unexpected <Invalid> error. I believe this is also the cause behind #3041 where the serialization function is forcefully converting a string-enum value to a hard string, turning an enum PRIMARY into "PRIMARY".
Possible Solution:
I have found a working solution for the string serialization problem, but not one for the enum one for now.
Instead of replacing a handful of special characters with their escaped version, we can directly use a JSON string to serialize strings.
Replacement code in src/data-connect/data-connect-api-client-internal.ts:
if (typeof data === 'string') {
return JSON.stringify(data); // Replace \ with \\, " with \", \n with \\n
}This takes care of the escaping and serializing strings properly and handles all the edge cases correctly.
This limitation impacts data seeding and administrative scripts that rely on the convenient insert/update methods for schemas with multiline text. Thank you for investigating!