@@ -27,27 +27,124 @@ const mapValue = (value) => {
2727 }
2828} ;
2929
30+ /**
31+ * Sets a nested value in an object using a dot-notated path.
32+ * @param {Object } obj - The object to modify.
33+ * @param {string } path - The dot-notated path to the value.
34+ * @param {* } value - The value to set.
35+ * @return {Object } The modified object.
36+ */
37+ function setNestedValue ( obj , path , value ) {
38+ const keys = path . split ( "." ) ;
39+ keys . reduce ( ( acc , key , index ) => {
40+ if ( index === keys . length - 1 ) {
41+ acc [ key ] = value ;
42+ return acc ;
43+ }
44+ if ( acc [ key ] === undefined ) {
45+ acc [ key ] = Number . isInteger ( + keys [ index + 1 ] ) ? [ ] : { } ;
46+ }
47+ return acc [ key ] ;
48+ } , obj ) ;
49+ return obj ;
50+ }
51+
52+ /**
53+ * Gets a nested value from an object using a dot-notated path.
54+ * @param {Object } obj - The object to retrieve the value from.
55+ * @param {string } path - The dot-notated path to the value.
56+ * @return {* } The value at the specified path, or undefined if not found.
57+ */
58+ function getNestedValue ( obj , path ) {
59+ const keys = path . split ( "." ) ;
60+ return keys . reduce ( ( current , key ) => {
61+ if ( current === undefined ) return undefined ;
62+ if ( Array . isArray ( current ) ) {
63+ return Number . isInteger ( + key ) ? current [ + key ] : current . map ( ( item ) => ( { [ key ] : item [ key ] } ) ) ;
64+ }
65+ return current [ key ] ;
66+ } , obj ) ;
67+ }
68+
69+ /**
70+ * Merges an array of objects into a single array, combining objects at the same index.
71+ * @param {Array<Object[]> } arrays - An array of object arrays to merge.
72+ * @return {Object[] } A merged array of objects.
73+ */
74+ function mergeArrays ( arrays ) {
75+ const maxLength = Math . max ( ...arrays . map ( ( arr ) => arr . length ) ) ;
76+ return Array . from ( { length : maxLength } , ( _ , i ) => Object . assign ( { } , ...arrays . map ( ( arr ) => arr [ i ] || { } ) ) ) ;
77+ }
78+
79+ /**
80+ * Extracts a field from the data and adds it to the accumulator.
81+ * @param {Object } data - The source data object.
82+ * @param {Object } acc - The accumulator object.
83+ * @param {string } field - The field to extract.
84+ * @return {Object } The updated accumulator.
85+ */
86+ function extractField ( data , acc , field ) {
87+ const value = getNestedValue ( data , field ) ;
88+ if ( value === undefined ) return acc ;
89+ const [ topLevelField ] = field . split ( "." ) ;
90+ const isArrayOfObjects = Array . isArray ( value ) && typeof value [ 0 ] === "object" ;
91+ if ( isArrayOfObjects ) {
92+ return {
93+ ...acc ,
94+ [ topLevelField ] : acc [ topLevelField ] ? mergeArrays ( [ acc [ topLevelField ] , value ] ) : value ,
95+ } ;
96+ } else {
97+ return setNestedValue ( acc , field , value ) ;
98+ }
99+ }
100+
101+ /**
102+ * Flattens a nested object, converting nested properties to dot-notation.
103+ * @param {Object } obj - The object to flatten.
104+ * @param {string } [prefix=""] - The prefix to use for flattened keys.
105+ * @return {Object } A new flattened object.
106+ */
107+ function flattenDocument ( obj , prefix = "" ) {
108+ return Object . keys ( obj ) . reduce ( ( acc , key ) => {
109+ const newKey = prefix ? `${ prefix } .${ key } ` : key ;
110+ const value = obj [ key ] ;
111+ // Handle primitive values (including null)
112+ if ( typeof value !== "object" || value === null ) {
113+ acc [ newKey ] = value ;
114+ return acc ;
115+ }
116+ // Handle arrays
117+ if ( Array . isArray ( value ) ) {
118+ if ( value . length === 0 || typeof value [ 0 ] !== "object" ) {
119+ acc [ newKey ] = value ;
120+ return acc ;
121+ }
122+ Object . keys ( value [ 0 ] ) . forEach ( ( subKey ) => {
123+ acc [ `${ newKey } .${ subKey } ` ] = value . map ( ( item ) => item [ subKey ] ) . filter ( ( v ) => v !== undefined ) ;
124+ } ) ;
125+ return acc ;
126+ }
127+ // Handle nested objects
128+ return { ...acc , ...flattenDocument ( value , newKey ) } ;
129+ } , { } ) ;
130+ }
131+
30132/**
31133 * @param {DocumentSnapshot } firestoreDocumentSnapshot
32134 * @param {Array } fieldsToExtract
33135 * @return {Object } typesenseDocument
34136 */
35137exports . typesenseDocumentFromSnapshot = async ( firestoreDocumentSnapshot , fieldsToExtract = config . firestoreCollectionFields ) => {
36- const flat = await import ( "flat" ) ;
37138 const data = firestoreDocumentSnapshot . data ( ) ;
38139
39- let entries = Object . entries ( data ) ;
40-
41- if ( fieldsToExtract . length ) {
42- entries = entries . filter ( ( [ key ] ) => fieldsToExtract . includes ( key ) ) ;
43- }
140+ const extractedData = fieldsToExtract . length === 0 ? data : fieldsToExtract . reduce ( ( acc , field ) => extractField ( data , acc , field ) , { } ) ;
44141
45- // Build a document with just the fields requested by the user, and mapped from Firestore types to Typesense types
46- const mappedDocument = Object . fromEntries ( entries . map ( ( [ key , value ] ) => [ key , mapValue ( value ) ] ) ) ;
142+ const mappedDocument = Object . fromEntries ( Object . entries ( extractedData ) . map ( ( [ key , value ] ) => [ key , mapValue ( value ) ] ) ) ;
47143
48144 // using flat to flatten nested objects for older versions of Typesense that did not support nested fields
49145 // https://typesense.org/docs/0.22.2/api/collections.html#indexing-nested-fields
50- const typesenseDocument = config . shouldFlattenNestedDocuments ? flat . flatten ( mappedDocument , { safe : true } ) : mappedDocument ;
146+ const typesenseDocument = config . shouldFlattenNestedDocuments ? flattenDocument ( mappedDocument ) : mappedDocument ;
147+ console . log ( "typesenseDocument" , typesenseDocument ) ;
51148
52149 typesenseDocument . id = firestoreDocumentSnapshot . id ;
53150
0 commit comments