Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
services:
typesense:
image: typesense/typesense:27.0
image: typesense/typesense:29.0
restart: on-failure
ports:
- "8108:8108"
Expand Down
14 changes: 14 additions & 0 deletions extension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -348,3 +348,17 @@ params:
value: true
default: false
required: false
- param: TYPESENSE_CONNECTION_TIMEOUT_SECONDS
label: Typesense Connection Timeout (seconds)
description: >-
The timeout in seconds for connections to Typesense. This applies to both connection timeout.
type: string
example: "60"
required: false
- param: TYPESENSE_RETRY_INTERVAL_SECONDS
label: Typesense Retry Interval (seconds)
description: >-
The retry interval in seconds for connections to Typesense. This applies to both connection timeout.
type: string
example: "60"
required: false
2 changes: 2 additions & 0 deletions functions/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ module.exports = {
typesenseProtocol: process.env.TYPESENSE_PROTOCOL || "https",
typesenseCollectionName: process.env.TYPESENSE_COLLECTION_NAME,
typesenseAPIKey: process.env.TYPESENSE_API_KEY,
typesenseConnectionTimeoutSeconds: parseInt(process.env.TYPESENSE_CONNECTION_TIMEOUT_SECONDS, 10),
typesenseRetryIntervalSeconds: parseInt(process.env.TYPESENSE_RETRY_INTERVAL_SECONDS, 10),
typesenseBackfillTriggerDocumentInFirestore: "typesense_sync/backfill",
typesenseBackfillBatchSize: 1000,
};
9 changes: 7 additions & 2 deletions functions/src/createTypesenseClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ function getRandomNumber(min, max) {
}

module.exports = function () {
const connectionTimeout =
!process.env.TYPESENSE_CONNECTION_TIMEOUT_SECONDS || Number.isNaN(config.typesenseConnectionTimeoutSeconds) ? getRandomNumber(60, 90) : config.typesenseConnectionTimeoutSeconds;

const retryInterval = !process.env.TYPESENSE_RETRY_INTERVAL_SECONDS || Number.isNaN(config.typesenseRetryIntervalSeconds) ? getRandomNumber(60, 120) : config.typesenseRetryIntervalSeconds;

return new Typesense.Client({
nodes: config.typesenseHosts.map((h) => {
return {
Expand All @@ -16,7 +21,7 @@ module.exports = function () {
};
}),
apiKey: config.typesenseAPIKey,
connectionTimeoutSeconds: getRandomNumber(60, 90),
retryIntervalSeconds: getRandomNumber(60, 120),
connectionTimeoutSeconds: connectionTimeout,
retryIntervalSeconds: retryInterval,
});
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
"scripts": {
"emulator": "cross-env DOTENV_CONFIG=extensions/test-params-flatten-nested-false.local.env firebase emulators:start --import=emulator_data",
"export": "firebase emulators:export emulator_data",
"test": "npm run test:flatttened && npm run test:unflattened && npm run test:subcollection",
"test": "npm run test:flatttened && npm run test:unflattened && npm run test:subcollection && npm run test:typesenseClientDefaults",
"test:flatttened": "cp -f extensions/test-params-flatten-nested-true.local.env functions/.env && cross-env NODE_OPTIONS=--experimental-vm-modules DOTENV_CONFIG=extensions/test-params-flatten-nested-true.local.env firebase emulators:exec --only functions,firestore,extensions 'jest --testRegex=\"WithFlattening\" --testRegex=\"backfill.spec\"'",
"test:unflattened": "cp -f extensions/test-params-flatten-nested-false.local.env functions/.env && cross-env NODE_OPTIONS=--experimental-vm-modules DOTENV_CONFIG=extensions/test-params-flatten-nested-false.local.env firebase emulators:exec --only functions,firestore,extensions 'jest --testRegex=\"WithoutFlattening\"'",
"test:subcollection": "jest --testRegex=\"writeLogging\" --testRegex=\"Subcollection\" --detectOpenHandles",
"test:typesenseClientDefaults": "jest --testRegex=\"typesenseClientDefaults\"",
"typesenseServer": "docker compose up",
"lint:fix": "eslint . --fix",
"lint": "eslint .",
Expand Down
115 changes: 115 additions & 0 deletions test/typesenseClientDefaults.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
describe("Typesense Client Default Random Configuration", () => {
let originalConnectionTimeout;
let originalRetryInterval;

beforeEach(() => {
originalConnectionTimeout = process.env.TYPESENSE_CONNECTION_TIMEOUT_SECONDS;
originalRetryInterval = process.env.TYPESENSE_RETRY_INTERVAL_SECONDS;

jest.resetModules();

process.env.TYPESENSE_HOSTS = "localhost";
process.env.TYPESENSE_API_KEY = "test-key";
process.env.TYPESENSE_COLLECTION_NAME = "test-collection";
});

afterEach(() => {
if (originalConnectionTimeout !== undefined) {
process.env.TYPESENSE_CONNECTION_TIMEOUT_SECONDS = originalConnectionTimeout;
} else {
delete process.env.TYPESENSE_CONNECTION_TIMEOUT_SECONDS;
}

if (originalRetryInterval !== undefined) {
process.env.TYPESENSE_RETRY_INTERVAL_SECONDS = originalRetryInterval;
} else {
delete process.env.TYPESENSE_RETRY_INTERVAL_SECONDS;
}

delete process.env.TYPESENSE_HOSTS;
delete process.env.TYPESENSE_API_KEY;
delete process.env.TYPESENSE_COLLECTION_NAME;

jest.resetModules();
});

describe("when environment variables are not set", () => {
beforeEach(() => {
delete process.env.TYPESENSE_CONNECTION_TIMEOUT_SECONDS;
delete process.env.TYPESENSE_RETRY_INTERVAL_SECONDS;

jest.resetModules();
});

it("should use random connection timeout between 60 and 90 seconds", () => {
const createTypesenseClient = require("../functions/src/createTypesenseClient");
const client = createTypesenseClient();

expect(client.configuration.connectionTimeoutSeconds).toBeGreaterThanOrEqual(60);
expect(client.configuration.connectionTimeoutSeconds).toBeLessThanOrEqual(90);
expect(Number.isInteger(client.configuration.connectionTimeoutSeconds)).toBe(true);
});

it("should use random retry interval between 60 and 120 seconds", () => {
const createTypesenseClient = require("../functions/src/createTypesenseClient");
const client = createTypesenseClient();

expect(client.configuration.retryIntervalSeconds).toBeGreaterThanOrEqual(60);
expect(client.configuration.retryIntervalSeconds).toBeLessThanOrEqual(120);
expect(Number.isInteger(client.configuration.retryIntervalSeconds)).toBe(true);
});

it("should generate different random values on multiple client creations", () => {
const createTypesenseClient = require("../functions/src/createTypesenseClient");
const clients = [];
const connectionTimeouts = new Set();
const retryIntervals = new Set();

for (let i = 0; i < 10; i++) {
const client = createTypesenseClient();
clients.push(client);
connectionTimeouts.add(client.configuration.connectionTimeoutSeconds);
retryIntervals.add(client.configuration.retryIntervalSeconds);
}

expect(connectionTimeouts.size).toBeGreaterThan(1);
expect(retryIntervals.size).toBeGreaterThan(1);
});
});

describe("when environment variables are set", () => {
beforeEach(() => {
process.env.TYPESENSE_CONNECTION_TIMEOUT_SECONDS = "45";
process.env.TYPESENSE_RETRY_INTERVAL_SECONDS = "30";

jest.resetModules();
});

it("should use the configured values instead of random ones", () => {
const createTypesenseClient = require("../functions/src/createTypesenseClient");
const client = createTypesenseClient();

expect(client.configuration.connectionTimeoutSeconds).toBe(45);
expect(client.configuration.retryIntervalSeconds).toBe(30);
});
});

describe("when environment variables are set to invalid values", () => {
beforeEach(() => {
process.env.TYPESENSE_CONNECTION_TIMEOUT_SECONDS = "invalid";
process.env.TYPESENSE_RETRY_INTERVAL_SECONDS = "also-invalid";

jest.resetModules();
});

it("should fall back to random values when parseInt returns NaN", () => {
const createTypesenseClient = require("../functions/src/createTypesenseClient");
const client = createTypesenseClient();

expect(client.configuration.connectionTimeoutSeconds).toBeGreaterThanOrEqual(60);
expect(client.configuration.connectionTimeoutSeconds).toBeLessThanOrEqual(90);
expect(client.configuration.retryIntervalSeconds).toBeGreaterThanOrEqual(60);
expect(client.configuration.retryIntervalSeconds).toBeLessThanOrEqual(120);
});
});
});