firestore-meilisearch 0.1.11 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.js CHANGED
@@ -51,4 +51,15 @@ module.exports = {
51
51
  },
52
52
  ],
53
53
  },
54
+ settings: {
55
+ 'import/parsers': {
56
+ '@typescript-eslint/parser': ['.ts', '.tsx'],
57
+ },
58
+ 'import/resolver': {
59
+ typescript: {
60
+ alwaysTryTypes: true, // always try to resolve types under `<root>@types` directory even it doesn't contain any source code, like `@types/unist`
61
+ project: './tsconfig.json',
62
+ },
63
+ },
64
+ },
54
65
  }
package/README.md CHANGED
@@ -8,8 +8,9 @@
8
8
 
9
9
  <h4 align="center">
10
10
  <a href="https://github.com/meilisearch/meilisearch">Meilisearch</a> |
11
+ <a href="https://www.meilisearch.com/cloud?utm_campaign=oss&utm_source=github&utm_medium=firestore-meilisearch">Meilisearch Cloud</a> |
11
12
  <a href="https://docs.meilisearch.com">Documentation</a> |
12
- <a href="https://slack.meilisearch.com">Slack</a> |
13
+ <a href="https://discord.meilisearch.com">Discord</a> |
13
14
  <a href="https://roadmap.meilisearch.com/tabs/1-under-consideration">Roadmap</a> |
14
15
  <a href="https://www.meilisearch.com">Website</a> |
15
16
  <a href="https://docs.meilisearch.com/faq">FAQ</a>
@@ -35,6 +36,10 @@ This extension listens to each creation, update, or deletion of your documents t
35
36
 
36
37
  Note that this extension only listens for changes to _documents_ in a specific collection, but not changes in any _subcollection_. However, you can install additional instances of this extension to listen to other collections in your Firestore database.
37
38
 
39
+ ## ⚡ Supercharge your Meilisearch experience
40
+
41
+ Say goodbye to server deployment and manual updates with [Meilisearch Cloud](https://www.meilisearch.com/cloud?utm_campaign=oss&utm_source=github&utm_medium=firestore-meilisearch). Get started with a 14-day free trial! No credit card required.
42
+
38
43
  #### Additional setup
39
44
 
40
45
  Before installing this extension, you'll need to:
@@ -118,7 +123,7 @@ firebase ext:install meilisearch/firestore-meilisearch --project=[your-project-i
118
123
 
119
124
  ## 🤖 Compatibility with Meilisearch
120
125
 
121
- This package only guarantees the compatibility with the [version v0.30.0 of Meilisearch](https://github.com/meilisearch/meilisearch/releases/tag/v0.30.0).
126
+ This package guarantees compatibility with [version v1.x of Meilisearch](https://github.com/meilisearch/meilisearch/releases/latest), but some features may not be present. Please check the [issues](https://github.com/meilisearch/firestore-meilisearch/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22+label%3Aenhancement) for more info.
122
127
 
123
128
  ## ⚙️ Development Workflow and Contributing
124
129
 
@@ -0,0 +1,27 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`extensions config Test MeilisearchIndex parameters param exists 1`] = `
4
+ {
5
+ "description": "What Meilisearch index do you want to index your data in?",
6
+ "example": "example: my_index",
7
+ "label": "Meilisearch Index Name",
8
+ "param": "MEILISEARCH_INDEX_NAME",
9
+ "required": true,
10
+ "type": "string",
11
+ "validationErrorMessage": "Must be a valid Index format. Index uid can be of type integer or string only composed of alphanumeric characters, hyphens (-) and underscores (_). Check out our guide on [index creation](https://docs.meilisearch.com/learn/core_concepts/indexes.html#index-creation).",
12
+ "validationRegex": "^[0-9A-Za-z_-]+$",
13
+ }
14
+ `;
15
+
16
+ exports[`extensions config Test fieldsToIndex parameter param exists 1`] = `
17
+ {
18
+ "default": "",
19
+ "description": "What fields do you want to index in Meilisearch? Create a comma-separated list of the field names, or leave it blank to include all fields. The id field is always indexed even when omitted from the list.",
20
+ "example": "example: name,description,...",
21
+ "label": "Fields to index in Meilisearch",
22
+ "param": "MEILISEARCH_FIELDS_TO_INDEX",
23
+ "required": false,
24
+ "validationErrorMessage": "Fields must be given through a comma-separated list.",
25
+ "validationRegex": "^[^,]?[a-zA-Z-_0-9,]*[^,]$",
26
+ }
27
+ `;
@@ -0,0 +1,3 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`getFieldsToIndex configuration detected from environment variables 1`] = `undefined`;
@@ -1,7 +1,10 @@
1
1
  import * as firebaseFunctionsTestInit from 'firebase-functions-test'
2
2
  import { mockConsoleInfo } from './__mocks__/console'
3
- import { firestore } from 'firebase-admin/lib/firestore'
4
- import { adaptDocument, adaptValues } from '../src/adapter'
3
+ import * as firestore from 'firebase-admin/firestore'
4
+ import {
5
+ adaptDocumentForMeilisearch,
6
+ adaptFieldsForMeilisearch,
7
+ } from '../src/meilisearch-adapter'
5
8
  import defaultDocument from './data/document'
6
9
 
7
10
  // Mocking of Firebase functions
@@ -15,7 +18,9 @@ describe('extensions process', () => {
15
18
  `docs/${defaultDocument.id}`
16
19
  )
17
20
 
18
- expect(adaptDocument(defaultDocument.id, snapshot, '')).toStrictEqual({
21
+ expect(
22
+ adaptDocumentForMeilisearch(defaultDocument.id, snapshot, '')
23
+ ).toStrictEqual({
19
24
  _firestore_id: defaultDocument.id,
20
25
  ...defaultDocument.document,
21
26
  })
@@ -27,7 +32,9 @@ describe('extensions process', () => {
27
32
  `docs/${defaultDocument.id}`
28
33
  )
29
34
 
30
- expect(adaptDocument(defaultDocument.id, snapshot, '')).toStrictEqual({
35
+ expect(
36
+ adaptDocumentForMeilisearch(defaultDocument.id, snapshot, '')
37
+ ).toStrictEqual({
31
38
  _firestore_id: defaultDocument.id,
32
39
  id: '12345',
33
40
  ...defaultDocument.document,
@@ -41,7 +48,7 @@ describe('extensions process', () => {
41
48
  )
42
49
 
43
50
  expect(
44
- adaptDocument(
51
+ adaptDocumentForMeilisearch(
45
52
  defaultDocument.id,
46
53
  snapshot,
47
54
  'title,overview,release_date'
@@ -57,21 +64,27 @@ describe('extensions process', () => {
57
64
 
58
65
  describe('adaptValues', () => {
59
66
  test('adaptValues an id value', () => {
60
- expect(adaptValues('id', defaultDocument.id as any)).toStrictEqual([
61
- 'id',
62
- defaultDocument.id,
63
- ])
67
+ expect(
68
+ adaptFieldsForMeilisearch(
69
+ { id: defaultDocument.id } as firestore.DocumentData,
70
+ 'id'
71
+ )
72
+ ).toStrictEqual({ id: defaultDocument.id })
64
73
  })
65
74
  test('adaptValues a geo point value', () => {
66
75
  const geoPoint = new firestore.GeoPoint(48.866667, 2.333333)
67
76
 
68
- expect(adaptValues('_geo', geoPoint)).toStrictEqual([
69
- '_geo',
70
- {
77
+ expect(
78
+ adaptFieldsForMeilisearch(
79
+ { _geo: geoPoint } as firestore.DocumentData,
80
+ '_geo'
81
+ )
82
+ ).toStrictEqual({
83
+ _geo: {
71
84
  lat: 48.866667,
72
85
  lng: 2.333333,
73
86
  },
74
- ])
87
+ })
75
88
  expect(mockConsoleInfo).toBeCalledWith(
76
89
  `A GeoPoint was found with the field name '_geo' for compatibility with Meilisearch the field 'latitude' was renamed to 'lat' and the field 'longitude' to 'lng'`
77
90
  )
@@ -79,13 +92,23 @@ describe('extensions process', () => {
79
92
  test('adaptValues a wrong geo point value', () => {
80
93
  const geoPoint = new firestore.GeoPoint(48.866667, 2.333333)
81
94
 
82
- expect(adaptValues('wrong_geo', geoPoint)).toStrictEqual([
83
- 'wrong_geo',
84
- geoPoint,
85
- ])
95
+ expect(
96
+ adaptFieldsForMeilisearch(
97
+ { wrong_geo: geoPoint } as firestore.DocumentData,
98
+ 'wrong_geo'
99
+ )
100
+ ).toStrictEqual({ wrong_geo: geoPoint })
86
101
  expect(mockConsoleInfo).toBeCalledWith(
87
102
  `A GeoPoint was found without the field name '_geo' if you want to use the geoSearch with Meilisearch rename it to '_geo'`
88
103
  )
89
104
  })
105
+ test('adaptValues with a _geo field at null', () => {
106
+ expect(
107
+ adaptFieldsForMeilisearch(
108
+ { _geo: null } as firestore.DocumentData,
109
+ '_geo'
110
+ )
111
+ ).toStrictEqual({})
112
+ })
90
113
  })
91
114
  })
@@ -1,6 +1,6 @@
1
1
  import * as firebaseFunctionsTestInit from 'firebase-functions-test'
2
2
  import mockedEnv from 'mocked-env'
3
- import { mocked } from 'ts-jest/utils'
3
+ import { mocked } from 'jest-mock'
4
4
  import {
5
5
  mockConsoleLog,
6
6
  mockConsoleInfo,
@@ -21,7 +21,7 @@ describe('extension', () => {
21
21
  let restoreEnv
22
22
 
23
23
  // Mocking of Meilisearch package
24
- const mockedMeilisearch = mocked(MeiliSearch, true)
24
+ const mockedMeilisearch = mocked(MeiliSearch)
25
25
  const mockedAddDocuments = jest.fn()
26
26
  const mockedDeleteDocument = jest.fn()
27
27
  const mockedIndex = jest.fn(() => ({
@@ -42,6 +42,7 @@ describe('extension', () => {
42
42
  beforeEach(() => {
43
43
  restoreEnv = mockedEnv(defaultEnvironment)
44
44
  config = require('../src/config').config
45
+ config.collectionPath = 'collection'
45
46
  })
46
47
  afterEach(() => restoreEnv())
47
48
 
@@ -1,6 +1,6 @@
1
1
  import * as firebaseFunctionsTestInit from 'firebase-functions-test'
2
2
  import mockedEnv from 'mocked-env'
3
- import { getChangeType, ChangeType, getChangedDocumentId } from '../src/util'
3
+ import { ChangeType, getChangedDocumentId, getChangeType } from '../src/util'
4
4
  import defaultEnvironment from './data/environment'
5
5
 
6
6
  describe('getChangeType', () => {
@@ -132,7 +132,7 @@ describe('getChangedDocumentId', () => {
132
132
  })
133
133
 
134
134
  describe('getFieldsToIndex', () => {
135
- let util
135
+ let adapter
136
136
  let restoreEnv
137
137
  let mockParseFieldsToIndex
138
138
  const config = global.config
@@ -149,32 +149,36 @@ describe('getFieldsToIndex', () => {
149
149
  })
150
150
 
151
151
  test('return empty list', () => {
152
- util = require('../src/util')
153
- mockParseFieldsToIndex = util.parseFieldsToIndex()
152
+ adapter = require('../src/meilisearch-adapter')
153
+ mockParseFieldsToIndex = adapter.parseFieldsToIndex()
154
154
  expect(mockParseFieldsToIndex).toMatchObject([])
155
155
  })
156
156
 
157
157
  test('return list with one field', () => {
158
- util = require('../src/util')
159
- mockParseFieldsToIndex = util.parseFieldsToIndex('field')
158
+ adapter = require('../src/meilisearch-adapter')
159
+ mockParseFieldsToIndex = adapter.parseFieldsToIndex('field')
160
160
  expect(mockParseFieldsToIndex).toMatchObject(['field'])
161
161
  })
162
162
 
163
163
  test('return list with multiple fields', () => {
164
- util = require('../src/util')
165
- mockParseFieldsToIndex = util.parseFieldsToIndex('field1,field2,field3')
164
+ adapter = require('../src/meilisearch-adapter')
165
+ mockParseFieldsToIndex = adapter.parseFieldsToIndex('field1,field2,field3')
166
166
  expect(mockParseFieldsToIndex).toMatchObject(['field1', 'field2', 'field3'])
167
167
  })
168
168
 
169
169
  test('return list with multiple fields and spaces', () => {
170
- util = require('../src/util')
171
- mockParseFieldsToIndex = util.parseFieldsToIndex('field1, field2, field3')
170
+ adapter = require('../src/meilisearch-adapter')
171
+ mockParseFieldsToIndex = adapter.parseFieldsToIndex(
172
+ 'field1, field2, field3'
173
+ )
172
174
  expect(mockParseFieldsToIndex).toMatchObject(['field1', 'field2', 'field3'])
173
175
  })
174
176
 
175
177
  test('return list of fiels with underscore', () => {
176
- util = require('../src/util')
177
- mockParseFieldsToIndex = util.parseFieldsToIndex('field_1,field_2,field_3')
178
+ adapter = require('../src/meilisearch-adapter')
179
+ mockParseFieldsToIndex = adapter.parseFieldsToIndex(
180
+ 'field_1,field_2,field_3'
181
+ )
178
182
  expect(mockParseFieldsToIndex).toMatchObject([
179
183
  'field_1',
180
184
  'field_2',
package/jest.config.js CHANGED
@@ -3,10 +3,13 @@ process.env.FIREBASE_CONFIG = '{}'
3
3
  module.exports = {
4
4
  rootDir: './',
5
5
  preset: 'ts-jest',
6
- globals: {
7
- 'ts-jest': {
8
- tsconfig: '<rootDir>/__tests__/tsconfig.json',
9
- },
6
+ transform: {
7
+ '^.+\\.[tj]sx?$': [
8
+ 'ts-jest',
9
+ {
10
+ tsconfig: '<rootDir>/__tests__/tsconfig.json',
11
+ },
12
+ ],
10
13
  },
11
14
  testEnvironment: 'node',
12
15
  testMatch: ['**/__tests__/*.test.ts'],
@@ -19,7 +19,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
19
19
  const admin = require("firebase-admin");
20
20
  const config_1 = require("./config");
21
21
  const logs = require("../logs");
22
- const adapter_1 = require("../adapter");
22
+ const meilisearch_adapter_1 = require("../meilisearch-adapter");
23
23
  const create_index_1 = require("../meilisearch/create-index");
24
24
  const run = async () => {
25
25
  // Retrieve all arguments from the commande line.
@@ -80,7 +80,7 @@ async function retrieveCollectionFromFirestore(database, config, index) {
80
80
  */
81
81
  async function sendDocumentsToMeilisearch(docs, index, fieldsToIndex) {
82
82
  const document = docs.map(snapshot => {
83
- return (0, adapter_1.adaptDocument)(snapshot.id, snapshot, fieldsToIndex);
83
+ return (0, meilisearch_adapter_1.adaptDocumentForMeilisearch)(snapshot.id, snapshot, fieldsToIndex);
84
84
  });
85
85
  try {
86
86
  await index.addDocuments(document, { primaryKey: '_firestore_id' });
package/lib/index.js CHANGED
@@ -21,7 +21,7 @@ const firebase_functions_1 = require("firebase-functions");
21
21
  const create_index_1 = require("./meilisearch/create-index");
22
22
  const util_1 = require("./util");
23
23
  const logs = require("./logs");
24
- const adapter_1 = require("./adapter");
24
+ const meilisearch_adapter_1 = require("./meilisearch-adapter");
25
25
  const config_1 = require("./config");
26
26
  const validate_1 = require("./validate");
27
27
  const index = (0, create_index_1.initMeilisearchIndex)(config_1.config.meilisearch);
@@ -30,19 +30,21 @@ logs.init();
30
30
  * IndexingWorker is responsible for aggregating a defined field from a Firestore collection into a Meilisearch index.
31
31
  * It is controlled by a Firestore handler.
32
32
  */
33
- exports.indexingWorker = functions.handler.firestore.document.onWrite(async (change) => {
33
+ exports.indexingWorker = functions.firestore
34
+ .document(config_1.config.collectionPath + '/{documentId}')
35
+ .onWrite(async (snapshot) => {
34
36
  logs.start();
35
- const changeType = (0, util_1.getChangeType)(change);
36
- const documentId = (0, util_1.getChangedDocumentId)(change);
37
+ const changeType = (0, util_1.getChangeType)(snapshot);
38
+ const documentId = (0, util_1.getChangedDocumentId)(snapshot);
37
39
  switch (changeType) {
38
40
  case util_1.ChangeType.CREATE:
39
- await handleAddDocument(documentId, change.after);
41
+ await handleAddDocument(documentId, snapshot.after);
40
42
  break;
41
43
  case util_1.ChangeType.DELETE:
42
44
  await handleDeleteDocument(documentId);
43
45
  break;
44
46
  case util_1.ChangeType.UPDATE:
45
- await handleUpdateDocument(documentId, change.after);
47
+ await handleUpdateDocument(documentId, snapshot.after);
46
48
  break;
47
49
  }
48
50
  logs.complete();
@@ -56,7 +58,7 @@ async function handleAddDocument(documentId, snapshot) {
56
58
  try {
57
59
  logs.addDocument(documentId);
58
60
  if ((0, validate_1.validateDocumentId)(documentId)) {
59
- const document = (0, adapter_1.adaptDocument)(documentId, snapshot, config_1.config.meilisearch.fieldsToIndex || '');
61
+ const document = (0, meilisearch_adapter_1.adaptDocumentForMeilisearch)(documentId, snapshot, config_1.config.meilisearch.fieldsToIndex || '');
60
62
  const { taskUid } = await index.addDocuments([document], {
61
63
  primaryKey: '_firestore_id',
62
64
  });
@@ -98,7 +100,7 @@ async function handleUpdateDocument(documentId, after) {
98
100
  try {
99
101
  logs.updateDocument(documentId);
100
102
  if ((0, validate_1.validateDocumentId)(documentId)) {
101
- const document = (0, adapter_1.adaptDocument)(documentId, after, config_1.config.meilisearch.fieldsToIndex || '');
103
+ const document = (0, meilisearch_adapter_1.adaptDocumentForMeilisearch)(documentId, after, config_1.config.meilisearch.fieldsToIndex || '');
102
104
  const { taskUid } = await index.addDocuments([document]);
103
105
  firebase_functions_1.logger.info(`Document update request for document with ID ${documentId} added to task list (task ID ${taskUid}).`);
104
106
  }
@@ -0,0 +1,90 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.adaptDocumentForMeilisearch = exports.adaptFieldsForMeilisearch = exports.parseFieldsToIndex = exports.isAFieldToIndex = void 0;
4
+ const firestore = require("firebase-admin/firestore");
5
+ const logs_1 = require("./logs");
6
+ /**
7
+ * Adapts GeoPoint Firestore instance to fit with Meilisearch geo point.
8
+ * @param {firestore.GeoPoint} geoPoint GeoPoint Firestore object.
9
+ * @return {MeilisearchGeoPoint} A properly formatted geo point for Meilisearch.
10
+ */
11
+ function adaptGeoPoint(geoPoint) {
12
+ return {
13
+ lat: geoPoint.latitude,
14
+ lng: geoPoint.longitude,
15
+ };
16
+ }
17
+ /**
18
+ * Check if the field is added to the document send to Meilisearch.
19
+ *
20
+ * @param {string[]} fieldsToIndex
21
+ * @param {string} key
22
+ * @return {boolean} true if it is a field that should be indexed in Meilisearch
23
+ *
24
+ */
25
+ function isAFieldToIndex(fieldsToIndex, key) {
26
+ if (fieldsToIndex.length === 0 ||
27
+ fieldsToIndex.includes('*') ||
28
+ fieldsToIndex.includes(key)) {
29
+ return true;
30
+ }
31
+ return false;
32
+ }
33
+ exports.isAFieldToIndex = isAFieldToIndex;
34
+ /**
35
+ * Parse the fieldsToIndex string into an array.
36
+ *
37
+ * @param {string} fieldsToIndex
38
+ * @return {string[]} An array of fields.
39
+ */
40
+ function parseFieldsToIndex(fieldsToIndex) {
41
+ return fieldsToIndex ? fieldsToIndex.split(/[ ,]+/) : [];
42
+ }
43
+ exports.parseFieldsToIndex = parseFieldsToIndex;
44
+ /**
45
+ * Update special fields to Meilisearch compatible format
46
+ * @param {firestore.DocumentData} document
47
+ * @param {string[]} rawFieldsToIndex
48
+ * @return {firestore.DocumentData} A properly formatted array of field and value.
49
+ */
50
+ function adaptFieldsForMeilisearch(document, rawFieldsToIndex) {
51
+ const fieldsToIndex = parseFieldsToIndex(rawFieldsToIndex);
52
+ return Object.keys(document).reduce((doc, currentField) => {
53
+ const value = document[currentField];
54
+ if (!isAFieldToIndex(fieldsToIndex, currentField))
55
+ return doc;
56
+ if (value instanceof firestore.GeoPoint) {
57
+ if (currentField === '_geo') {
58
+ (0, logs_1.infoGeoPoint)(true);
59
+ return {
60
+ ...doc,
61
+ _geo: adaptGeoPoint(value),
62
+ };
63
+ }
64
+ else {
65
+ (0, logs_1.infoGeoPoint)(false);
66
+ }
67
+ }
68
+ else if (currentField === '_geo') {
69
+ return doc;
70
+ }
71
+ return { ...doc, [currentField]: value };
72
+ }, {});
73
+ }
74
+ exports.adaptFieldsForMeilisearch = adaptFieldsForMeilisearch;
75
+ /**
76
+ * Adapts documents from the Firestore database to Meilisearch compatible documents.
77
+ * @param {string} documentId Document id.
78
+ * @param {DocumentSnapshot} snapshot Snapshot of the data contained in the document read from your Firestore database.
79
+ * @param {string} rawFieldsToIndex Value of the setting `FIELDS_TO_INDEX`
80
+ * @return {Record<string, any>} A properly formatted document to be added or updated in Meilisearch.
81
+ */
82
+ function adaptDocumentForMeilisearch(documentId, snapshot, rawFieldsToIndex) {
83
+ const data = snapshot.data() || {};
84
+ if ('_firestore_id' in data) {
85
+ delete data.id;
86
+ }
87
+ const adaptedDoc = adaptFieldsForMeilisearch(data, rawFieldsToIndex);
88
+ return { _firestore_id: documentId, ...adaptedDoc };
89
+ }
90
+ exports.adaptDocumentForMeilisearch = adaptDocumentForMeilisearch;
package/lib/util.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sanitizeDocuments = exports.parseFieldsToIndex = exports.getChangedDocumentId = exports.getChangeType = exports.ChangeType = void 0;
3
+ exports.getChangedDocumentId = exports.getChangeType = exports.ChangeType = void 0;
4
4
  var ChangeType;
5
5
  (function (ChangeType) {
6
6
  ChangeType[ChangeType["CREATE"] = 0] = "CREATE";
@@ -34,36 +34,3 @@ function getChangedDocumentId(change) {
34
34
  return change.before.id;
35
35
  }
36
36
  exports.getChangedDocumentId = getChangedDocumentId;
37
- /**
38
- * Parse the fieldsToIndex string into an array.
39
- *
40
- * @param {string} fieldsToIndex
41
- * @return {string[]} An array of fields.
42
- */
43
- function parseFieldsToIndex(fieldsToIndex) {
44
- return fieldsToIndex ? fieldsToIndex.split(/[ ,]+/) : [];
45
- }
46
- exports.parseFieldsToIndex = parseFieldsToIndex;
47
- /**
48
- * Remove unwanted fields from the document before it is send to Meilisearch.
49
- *
50
- * @param {string[]} fieldsToIndex
51
- * @param {Record<string, any>} document
52
- * @return {Record<string, any>} sanitized document
53
- *
54
- */
55
- function sanitizeDocuments(fieldsToIndex, document) {
56
- if (fieldsToIndex.length === 0) {
57
- return document;
58
- }
59
- if (fieldsToIndex.includes('*')) {
60
- return document;
61
- }
62
- for (const key in document) {
63
- if (!fieldsToIndex.includes(key)) {
64
- delete document[key];
65
- }
66
- }
67
- return document;
68
- }
69
- exports.sanitizeDocuments = sanitizeDocuments;
package/lib/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.version = void 0;
4
- exports.version = '0.1.11';
4
+ exports.version = '0.3.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firestore-meilisearch",
3
- "version": "0.1.11",
3
+ "version": "0.3.0",
4
4
  "scripts": {
5
5
  "lint": "eslint .",
6
6
  "lint:fix": "eslint . --fix",
@@ -17,13 +17,13 @@
17
17
  "test:coverage": "jest --coverage"
18
18
  },
19
19
  "engines": {
20
- "node": ">=14.0.0"
20
+ "node": ">=20.0.0"
21
21
  },
22
22
  "main": "lib/index.js",
23
23
  "dependencies": {
24
24
  "commander": "^9.1.0",
25
- "firebase-admin": "^9.8.0",
26
- "firebase-functions": "^3.16.0",
25
+ "firebase-admin": "^11.5.0",
26
+ "firebase-functions": "^4.2.1",
27
27
  "inquirer": "^8.2.2",
28
28
  "meilisearch": "^0.30.0"
29
29
  },
@@ -35,16 +35,17 @@
35
35
  "eslint": "^7.6.0",
36
36
  "eslint-config-google": "^0.14.0",
37
37
  "eslint-config-prettier": "^8.3.0",
38
+ "eslint-import-resolver-typescript": "^3.5.3",
38
39
  "eslint-plugin-import": "^2.24.2",
39
40
  "eslint-plugin-jest": "^24.4.2",
40
41
  "eslint-plugin-prettier": "^4.0.0",
41
- "firebase-functions-test": "^0.3.2",
42
- "jest": "^27.2.2",
43
- "jest-mock": "^27.1.1",
42
+ "firebase-functions-test": "^3.0.0",
43
+ "jest": "^29.4.3",
44
+ "jest-mock": "^29.4.3",
44
45
  "js-yaml": "^4.1.0",
45
46
  "mocked-env": "^1.3.5",
46
47
  "prettier": "^2.4.1",
47
- "ts-jest": "^27.0.5",
48
+ "ts-jest": "^29.0.5",
48
49
  "ts-node": "^10.2.1",
49
50
  "typescript": "^4.4.3"
50
51
  },
@@ -17,10 +17,10 @@
17
17
  */
18
18
 
19
19
  import * as admin from 'firebase-admin'
20
- import { DocumentSnapshot } from 'firebase-functions/lib/providers/firestore'
20
+ import { DocumentSnapshot } from 'firebase-functions/lib/v1/providers/firestore'
21
21
  import { CLIConfig, parseConfig } from './config'
22
22
  import * as logs from '../logs'
23
- import { adaptDocument } from '../adapter'
23
+ import { adaptDocumentForMeilisearch } from '../meilisearch-adapter'
24
24
  import { initMeilisearchIndex } from '../meilisearch/create-index'
25
25
  import { Index } from '../types'
26
26
 
@@ -106,7 +106,7 @@ async function sendDocumentsToMeilisearch(
106
106
  fieldsToIndex: string
107
107
  ): Promise<number> {
108
108
  const document = docs.map(snapshot => {
109
- return adaptDocument(snapshot.id, snapshot, fieldsToIndex)
109
+ return adaptDocumentForMeilisearch(snapshot.id, snapshot, fieldsToIndex)
110
110
  })
111
111
  try {
112
112
  await index.addDocuments(document, { primaryKey: '_firestore_id' })
package/src/index.ts CHANGED
@@ -17,11 +17,11 @@
17
17
 
18
18
  import * as functions from 'firebase-functions'
19
19
  import { Change, logger } from 'firebase-functions'
20
- import { DocumentSnapshot } from 'firebase-functions/lib/providers/firestore'
20
+ import { DocumentSnapshot } from 'firebase-functions/lib/v1/providers/firestore'
21
21
  import { initMeilisearchIndex } from './meilisearch/create-index'
22
22
  import { getChangeType, getChangedDocumentId, ChangeType } from './util'
23
23
  import * as logs from './logs'
24
- import { adaptDocument } from './adapter'
24
+ import { adaptDocumentForMeilisearch } from './meilisearch-adapter'
25
25
  import { config } from './config'
26
26
  import { validateDocumentId } from './validate'
27
27
 
@@ -33,26 +33,26 @@ logs.init()
33
33
  * IndexingWorker is responsible for aggregating a defined field from a Firestore collection into a Meilisearch index.
34
34
  * It is controlled by a Firestore handler.
35
35
  */
36
- export const indexingWorker = functions.handler.firestore.document.onWrite(
37
- async (change: Change<DocumentSnapshot>): Promise<void> => {
36
+ export const indexingWorker = functions.firestore
37
+ .document(config.collectionPath + '/{documentId}')
38
+ .onWrite(async (snapshot: Change<DocumentSnapshot>): Promise<void> => {
38
39
  logs.start()
39
- const changeType = getChangeType(change)
40
- const documentId = getChangedDocumentId(change)
40
+ const changeType = getChangeType(snapshot)
41
+ const documentId = getChangedDocumentId(snapshot)
41
42
 
42
43
  switch (changeType) {
43
44
  case ChangeType.CREATE:
44
- await handleAddDocument(documentId, change.after)
45
+ await handleAddDocument(documentId, snapshot.after)
45
46
  break
46
47
  case ChangeType.DELETE:
47
48
  await handleDeleteDocument(documentId)
48
49
  break
49
50
  case ChangeType.UPDATE:
50
- await handleUpdateDocument(documentId, change.after)
51
+ await handleUpdateDocument(documentId, snapshot.after)
51
52
  break
52
53
  }
53
54
  logs.complete()
54
- }
55
- )
55
+ })
56
56
 
57
57
  /**
58
58
  * Handle addition of a document in the Meilisearch index.
@@ -66,7 +66,7 @@ async function handleAddDocument(
66
66
  try {
67
67
  logs.addDocument(documentId)
68
68
  if (validateDocumentId(documentId)) {
69
- const document = adaptDocument(
69
+ const document = adaptDocumentForMeilisearch(
70
70
  documentId,
71
71
  snapshot,
72
72
  config.meilisearch.fieldsToIndex || ''
@@ -123,7 +123,7 @@ async function handleUpdateDocument(
123
123
  try {
124
124
  logs.updateDocument(documentId)
125
125
  if (validateDocumentId(documentId)) {
126
- const document = adaptDocument(
126
+ const document = adaptDocumentForMeilisearch(
127
127
  documentId,
128
128
  after,
129
129
  config.meilisearch.fieldsToIndex || ''
@@ -0,0 +1,120 @@
1
+ 'use strict'
2
+ /*
3
+ * Copyright 2022 Meilisearch
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * https://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ import { DocumentSnapshot } from 'firebase-functions/lib/v1/providers/firestore'
19
+ import * as firestore from 'firebase-admin/firestore'
20
+ import { infoGeoPoint } from './logs'
21
+
22
+ type MeilisearchGeoPoint = {
23
+ lat: number
24
+ lng: number
25
+ }
26
+
27
+ /**
28
+ * Adapts GeoPoint Firestore instance to fit with Meilisearch geo point.
29
+ * @param {firestore.GeoPoint} geoPoint GeoPoint Firestore object.
30
+ * @return {MeilisearchGeoPoint} A properly formatted geo point for Meilisearch.
31
+ */
32
+ function adaptGeoPoint(geoPoint: firestore.GeoPoint): MeilisearchGeoPoint {
33
+ return {
34
+ lat: geoPoint.latitude,
35
+ lng: geoPoint.longitude,
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Check if the field is added to the document send to Meilisearch.
41
+ *
42
+ * @param {string[]} fieldsToIndex
43
+ * @param {string} key
44
+ * @return {boolean} true if it is a field that should be indexed in Meilisearch
45
+ *
46
+ */
47
+ export function isAFieldToIndex(fieldsToIndex: string[], key: string): boolean {
48
+ if (
49
+ fieldsToIndex.length === 0 ||
50
+ fieldsToIndex.includes('*') ||
51
+ fieldsToIndex.includes(key)
52
+ ) {
53
+ return true
54
+ }
55
+ return false
56
+ }
57
+
58
+ /**
59
+ * Parse the fieldsToIndex string into an array.
60
+ *
61
+ * @param {string} fieldsToIndex
62
+ * @return {string[]} An array of fields.
63
+ */
64
+ export function parseFieldsToIndex(fieldsToIndex: string): string[] {
65
+ return fieldsToIndex ? fieldsToIndex.split(/[ ,]+/) : []
66
+ }
67
+
68
+ /**
69
+ * Update special fields to Meilisearch compatible format
70
+ * @param {firestore.DocumentData} document
71
+ * @param {string[]} rawFieldsToIndex
72
+ * @return {firestore.DocumentData} A properly formatted array of field and value.
73
+ */
74
+ export function adaptFieldsForMeilisearch(
75
+ document: firestore.DocumentData,
76
+ rawFieldsToIndex: string
77
+ ): firestore.DocumentData {
78
+ const fieldsToIndex = parseFieldsToIndex(rawFieldsToIndex)
79
+
80
+ return Object.keys(document).reduce((doc, currentField) => {
81
+ const value = document[currentField]
82
+
83
+ if (!isAFieldToIndex(fieldsToIndex, currentField)) return doc
84
+ if (value instanceof firestore.GeoPoint) {
85
+ if (currentField === '_geo') {
86
+ infoGeoPoint(true)
87
+ return {
88
+ ...doc,
89
+ _geo: adaptGeoPoint(value),
90
+ }
91
+ } else {
92
+ infoGeoPoint(false)
93
+ }
94
+ } else if (currentField === '_geo') {
95
+ return doc
96
+ }
97
+ return { ...doc, [currentField]: value }
98
+ }, {})
99
+ }
100
+
101
+ /**
102
+ * Adapts documents from the Firestore database to Meilisearch compatible documents.
103
+ * @param {string} documentId Document id.
104
+ * @param {DocumentSnapshot} snapshot Snapshot of the data contained in the document read from your Firestore database.
105
+ * @param {string} rawFieldsToIndex Value of the setting `FIELDS_TO_INDEX`
106
+ * @return {Record<string, any>} A properly formatted document to be added or updated in Meilisearch.
107
+ */
108
+ export function adaptDocumentForMeilisearch(
109
+ documentId: string,
110
+ snapshot: DocumentSnapshot,
111
+ rawFieldsToIndex: string
112
+ ): Record<string, any> {
113
+ const data = snapshot.data() || {}
114
+ if ('_firestore_id' in data) {
115
+ delete data.id
116
+ }
117
+ const adaptedDoc = adaptFieldsForMeilisearch(data, rawFieldsToIndex)
118
+
119
+ return { _firestore_id: documentId, ...adaptedDoc }
120
+ }
package/src/util.ts CHANGED
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
 
18
- import { DocumentSnapshot } from 'firebase-functions/lib/providers/firestore'
18
+ import { DocumentSnapshot } from 'firebase-functions/lib/v1/providers/firestore'
19
19
  import { Change } from 'firebase-functions'
20
20
 
21
21
  export enum ChangeType {
@@ -50,40 +50,3 @@ export function getChangedDocumentId(change: Change<DocumentSnapshot>): string {
50
50
  }
51
51
  return change.before.id
52
52
  }
53
-
54
- /**
55
- * Parse the fieldsToIndex string into an array.
56
- *
57
- * @param {string} fieldsToIndex
58
- * @return {string[]} An array of fields.
59
- */
60
- export function parseFieldsToIndex(fieldsToIndex: string): string[] {
61
- return fieldsToIndex ? fieldsToIndex.split(/[ ,]+/) : []
62
- }
63
-
64
- /**
65
- * Remove unwanted fields from the document before it is send to Meilisearch.
66
- *
67
- * @param {string[]} fieldsToIndex
68
- * @param {Record<string, any>} document
69
- * @return {Record<string, any>} sanitized document
70
- *
71
- */
72
- export function sanitizeDocuments(
73
- fieldsToIndex: string[],
74
- document: Record<string, any>
75
- ): Record<string, any> {
76
- if (fieldsToIndex.length === 0) {
77
- return document
78
- }
79
- if (fieldsToIndex.includes('*')) {
80
- return document
81
- }
82
-
83
- for (const key in document) {
84
- if (!fieldsToIndex.includes(key)) {
85
- delete document[key]
86
- }
87
- }
88
- return document
89
- }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '0.1.11'
1
+ export const version = '0.3.0'
package/lib/adapter.js DELETED
@@ -1,53 +0,0 @@
1
- 'use strict';
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.adaptValues = exports.adaptDocument = void 0;
4
- const firestore_1 = require("firebase-admin/lib/firestore");
5
- const util_1 = require("./util");
6
- const logs = require("./logs");
7
- /**
8
- * Adapts documents from the Firestore database to Meilisearch compatible documents.
9
- * @param {string} documentId Document id.
10
- * @param {DocumentSnapshot} snapshot Snapshot of the data contained in the document read from your Firestore database.
11
- * @param {string} fieldsToIndex list of fields added in the document send to Meilisearch.
12
- * @return {Record<string, any>} A properly formatted document to be added or updated in Meilisearch.
13
- */
14
- function adaptDocument(documentId, snapshot, fieldsToIndex) {
15
- const fields = (0, util_1.parseFieldsToIndex)(fieldsToIndex);
16
- const data = snapshot.data() || {};
17
- if ('_firestore_id' in data) {
18
- delete data.id;
19
- }
20
- const document = (0, util_1.sanitizeDocuments)(fields, data);
21
- return { _firestore_id: documentId, ...document };
22
- }
23
- exports.adaptDocument = adaptDocument;
24
- /**
25
- * Checks and adapts each values to be compatible with Meilisearch documents.
26
- * @param {string} field
27
- * @param {FirestoreRow} value
28
- * @return {[string,FirestoreRow]} A properly formatted array of field and value.
29
- */
30
- function adaptValues(field, value) {
31
- if (value instanceof firestore_1.firestore.GeoPoint) {
32
- if (field === '_geo') {
33
- logs.infoGeoPoint(true);
34
- return [field, adaptGeoPoint(value)];
35
- }
36
- else {
37
- logs.infoGeoPoint(false);
38
- }
39
- }
40
- return [field, value];
41
- }
42
- exports.adaptValues = adaptValues;
43
- /**
44
- * Adapts GeoPoint Firestore instance to fit with Meilisearch geo point.
45
- * @param {firestore.GeoPoint} geoPoint GeoPoint Firestore object.
46
- * @return {MeilisearchGeoPoint} A properly formatted geo point for Meilisearch.
47
- */
48
- const adaptGeoPoint = (geoPoint) => {
49
- return {
50
- lat: geoPoint.latitude,
51
- lng: geoPoint.longitude,
52
- };
53
- };
@@ -1,17 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createMeiliSearchIndex = void 0;
4
- const meilisearch_1 = require("meilisearch");
5
- /**
6
- * createMeiliSearchIndex
7
- * @param {MeiliSearchConfig}
8
- * @return {Index}
9
- */
10
- function createMeiliSearchIndex({ host, apiKey, indexUid, }) {
11
- const client = new meilisearch_1.MeiliSearch({
12
- host,
13
- apiKey,
14
- });
15
- return client.index(indexUid);
16
- }
17
- exports.createMeiliSearchIndex = createMeiliSearchIndex;
package/src/adapter.ts DELETED
@@ -1,95 +0,0 @@
1
- 'use strict'
2
- /*
3
- * Copyright 2022 Meilisearch
4
- *
5
- * Licensed under the Apache License, Version 2.0 (the "License");
6
- * you may not use this file except in compliance with the License.
7
- * You may obtain a copy of the License at
8
- *
9
- * https://www.apache.org/licenses/LICENSE-2.0
10
- *
11
- * Unless required by applicable law or agreed to in writing, software
12
- * distributed under the License is distributed on an "AS IS" BASIS,
13
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- * See the License for the specific language governing permissions and
15
- * limitations under the License.
16
- */
17
-
18
- import { DocumentSnapshot } from 'firebase-functions/lib/providers/firestore'
19
- import { firestore } from 'firebase-admin/lib/firestore'
20
- import { parseFieldsToIndex, sanitizeDocuments } from './util'
21
- import * as logs from './logs'
22
-
23
- type MeilisearchGeoPoint = {
24
- lat: number
25
- lng: number
26
- }
27
-
28
- type FirestoreRow =
29
- | null
30
- | boolean
31
- | number
32
- | string
33
- | firestore.DocumentReference
34
- | firestore.GeoPoint
35
- | firestore.Timestamp
36
- | Array<any>
37
- | Map<any, any>
38
- | MeilisearchGeoPoint
39
-
40
- /**
41
- * Adapts documents from the Firestore database to Meilisearch compatible documents.
42
- * @param {string} documentId Document id.
43
- * @param {DocumentSnapshot} snapshot Snapshot of the data contained in the document read from your Firestore database.
44
- * @param {string} fieldsToIndex list of fields added in the document send to Meilisearch.
45
- * @return {Record<string, any>} A properly formatted document to be added or updated in Meilisearch.
46
- */
47
- export function adaptDocument(
48
- documentId: string,
49
- snapshot: DocumentSnapshot,
50
- fieldsToIndex: string
51
- ): Record<string, any> {
52
- const fields = parseFieldsToIndex(fieldsToIndex)
53
-
54
- const data = snapshot.data() || {}
55
- if ('_firestore_id' in data) {
56
- delete data.id
57
- }
58
-
59
- const document = sanitizeDocuments(fields, data)
60
-
61
- return { _firestore_id: documentId, ...document }
62
- }
63
-
64
- /**
65
- * Checks and adapts each values to be compatible with Meilisearch documents.
66
- * @param {string} field
67
- * @param {FirestoreRow} value
68
- * @return {[string,FirestoreRow]} A properly formatted array of field and value.
69
- */
70
- export function adaptValues(
71
- field: string,
72
- value: FirestoreRow
73
- ): [string, FirestoreRow | MeilisearchGeoPoint] {
74
- if (value instanceof firestore.GeoPoint) {
75
- if (field === '_geo') {
76
- logs.infoGeoPoint(true)
77
- return [field, adaptGeoPoint(value)]
78
- } else {
79
- logs.infoGeoPoint(false)
80
- }
81
- }
82
- return [field, value]
83
- }
84
-
85
- /**
86
- * Adapts GeoPoint Firestore instance to fit with Meilisearch geo point.
87
- * @param {firestore.GeoPoint} geoPoint GeoPoint Firestore object.
88
- * @return {MeilisearchGeoPoint} A properly formatted geo point for Meilisearch.
89
- */
90
- const adaptGeoPoint = (geoPoint: firestore.GeoPoint): MeilisearchGeoPoint => {
91
- return {
92
- lat: geoPoint.latitude,
93
- lng: geoPoint.longitude,
94
- }
95
- }