Skip to content

feat(clients): add new realtime-personalization api #4613

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Mar 25, 2025
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
targets:
$default:
builders:
json_serializable:
options:
any_map: false
checked: true
create_factory: true
create_to_json: true
disallow_unrecognized_keys: false
explicit_to_json: true
field_rename: none
ignore_unannotated: false
include_if_null: false
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

2 changes: 1 addition & 1 deletion clients/algoliasearch-client-javascript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"packages/*"
],
"scripts": {
"build": "lerna run build --skip-nx-cache --scope '@algolia/requester-testing' --scope '@algolia/logger-console' --scope 'algoliasearch' --scope '@algolia/client-composition' --scope '@algolia/composition' --include-dependencies",
"build": "lerna run build --skip-nx-cache --scope '@algolia/requester-testing' --scope '@algolia/logger-console' --scope 'algoliasearch' --scope '@algolia/client-composition' --scope '@algolia/composition' --scope '@algolia/client-realtime-personalization' --include-dependencies",
"clean": "lerna run clean",
"release:publish": "tsc --project scripts/tsconfig.json && node scripts/dist/publish.js",
"test": "lerna run test $*",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"version": "0.0.1-alpha.0",
"repository": {
"type": "git",
"url": "git+https://github.com/algolia/algoliasearch-client-javascript.git"
},
"homepage": "https://github.com/algolia/algoliasearch-client-javascript/packages/client-realtime-personalization#readme",
"type": "module",
"license": "MIT",
"author": "Algolia",
"scripts": {
"build": "yarn clean && yarn tsup && yarn rollup -c rollup.config.js",
"clean": "rm -rf ./dist || true",
"test:bundle": "publint . && attw --pack ."
},
"name": "@algolia/client-realtime-personalization",
"description": "JavaScript client for client-realtime-personalization",
"exports": {
".": {
"node": {
"types": {
"import": "./dist/node.d.ts",
"module": "./dist/node.d.ts",
"require": "./dist/node.d.cts"
},
"import": "./dist/builds/node.js",
"module": "./dist/builds/node.js",
"require": "./dist/builds/node.cjs"
},
"worker": {
"types": "./dist/worker.d.ts",
"default": "./dist/builds/worker.js"
},
"default": {
"types": "./dist/browser.d.ts",
"module": "./dist/builds/browser.js",
"import": "./dist/builds/browser.js",
"default": "./dist/builds/browser.umd.js"
}
},
"./dist/builds/*": "./dist/builds/*.js"
},
"jsdelivr": "./dist/builds/browser.umd.js",
"unpkg": "./dist/builds/browser.umd.js",
"react-native": "./dist/builds/browser.js",
"files": [
"dist",
"index.js",
"index.d.ts"
],
"dependencies": {
"@algolia/client-common": "5.22.0",
"@algolia/requester-browser-xhr": "5.22.0",
"@algolia/requester-fetch": "5.22.0",
"@algolia/requester-node-http": "5.22.0"
},
"devDependencies": {
"@arethetypeswrong/cli": "0.17.4",
"@types/node": "22.13.11",
"publint": "0.3.9",
"rollup": "4.36.0",
"tsup": "8.4.0",
"typescript": "5.7.3"
},
"engines": {
"node": ">= 14.0.0"
}
}
17 changes: 17 additions & 0 deletions clients/algoliasearch-client-javascript/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,23 @@ __metadata:
languageName: unknown
linkType: soft

"@algolia/client-realtime-personalization@workspace:packages/client-realtime-personalization":
version: 0.0.0-use.local
resolution: "@algolia/client-realtime-personalization@workspace:packages/client-realtime-personalization"
dependencies:
"@algolia/client-common": "npm:5.22.0"
"@algolia/requester-browser-xhr": "npm:5.22.0"
"@algolia/requester-fetch": "npm:5.22.0"
"@algolia/requester-node-http": "npm:5.22.0"
"@arethetypeswrong/cli": "npm:0.17.4"
"@types/node": "npm:22.13.11"
publint: "npm:0.3.9"
rollup: "npm:4.36.0"
tsup: "npm:8.4.0"
typescript: "npm:5.7.3"
languageName: unknown
linkType: soft

"@algolia/client-search@npm:5.22.0, @algolia/client-search@workspace:packages/client-search":
version: 0.0.0-use.local
resolution: "@algolia/client-search@workspace:packages/client-search"
Expand Down
35 changes: 26 additions & 9 deletions config/clients.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"personalization",
"query-suggestions",
"recommend",
"search"
"search",
"realtime-personalization"
],
"folder": "clients/algoliasearch-client-csharp",
"gitRepoId": "algoliasearch-client-csharp",
Expand Down Expand Up @@ -48,6 +49,10 @@
{
"name": "search",
"output": "clients/algoliasearch-client-dart/packages/client_search"
},
{
"name": "realtime-personalization",
"output": "clients/algoliasearch-client-dart/packages/client_realtime_personalization"
}
],
"folder": "clients/algoliasearch-client-dart",
Expand Down Expand Up @@ -76,7 +81,8 @@
"personalization",
"query-suggestions",
"recommend",
"search"
"search",
"realtime-personalization"
],
"folder": "clients/algoliasearch-client-go",
"gitRepoId": "algoliasearch-client-go",
Expand Down Expand Up @@ -104,7 +110,8 @@
"personalization",
"query-suggestions",
"recommend",
"search"
"search",
"realtime-personalization"
],
"folder": "clients/algoliasearch-client-java",
"gitRepoId": "algoliasearch-client-java",
Expand Down Expand Up @@ -176,6 +183,10 @@
{
"name": "composition-full",
"output": "clients/algoliasearch-client-javascript/packages/client-composition"
},
{
"name": "realtime-personalization",
"output": "clients/algoliasearch-client-javascript/packages/client-realtime-personalization"
}
],
"folder": "clients/algoliasearch-client-javascript",
Expand Down Expand Up @@ -203,7 +214,8 @@
"personalization",
"query-suggestions",
"recommend",
"search"
"search",
"realtime-personalization"
],
"folder": "clients/algoliasearch-client-kotlin",
"gitRepoId": "algoliasearch-client-kotlin",
Expand Down Expand Up @@ -231,7 +243,8 @@
"personalization",
"query-suggestions",
"recommend",
"search"
"search",
"realtime-personalization"
],
"folder": "clients/algoliasearch-client-php",
"gitRepoId": "algoliasearch-client-php",
Expand Down Expand Up @@ -259,7 +272,8 @@
"personalization",
"query-suggestions",
"recommend",
"search"
"search",
"realtime-personalization"
],
"folder": "clients/algoliasearch-client-python",
"gitRepoId": "algoliasearch-client-python",
Expand Down Expand Up @@ -294,7 +308,8 @@
"personalization",
"query-suggestions",
"recommend",
"search"
"search",
"realtime-personalization"
],
"folder": "clients/algoliasearch-client-ruby",
"gitRepoId": "algoliasearch-client-ruby",
Expand Down Expand Up @@ -322,7 +337,8 @@
"personalization",
"query-suggestions",
"recommend",
"search"
"search",
"realtime-personalization"
],
"folder": "clients/algoliasearch-client-scala",
"gitRepoId": "algoliasearch-client-scala",
Expand All @@ -349,7 +365,8 @@
"personalization",
"query-suggestions",
"recommend",
"search"
"search",
"realtime-personalization"
],
"folder": "clients/algoliasearch-client-swift",
"gitRepoId": "algoliasearch-client-swift",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ public String getName() {
public void processOpts() {
String client = (String) additionalProperties.get("client");

additionalProperties.put("packageName", client.equals("query-suggestions") ? "suggestions" : client);
additionalProperties.put("packageName", client.equals("query-suggestions") ? "suggestions" : Helpers.camelize(client));
additionalProperties.put("enumClassPrefix", true);
additionalProperties.put("is" + Helpers.capitalize(Helpers.camelize((String) additionalProperties.get("client"))) + "Client", true);
additionalProperties.put("is" + Helpers.capitalize(Helpers.camelize(client)) + "Client", true);

String outputFolder = "algolia" + File.separator + client;
setOutputDir(getOutputDir() + File.separator + outputFolder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public Map<String, Object> postProcessSupportingFileData(Map<String, Object> obj
// We can put whatever we want in the bundle, and it will be accessible in the template
bundle.put("mode", mode);
bundle.put("is" + Helpers.capitalize(Helpers.camelize(client)) + "Client", true);
bundle.put("isStandaloneClient", client.contains("search") || client.contains("composition"));
bundle.put("isStandaloneClient", client.contains("search") || client.contains("composition") || client.contains("realtime"));
bundle.put("isSearchClient", client.contains("search")); // just so algoliasearch is treated as a search client too
bundle.put("client", Helpers.createClientName(importClientName, language) + "Client");
bundle.put("clientPrefix", Helpers.createClientName(importClientName, language));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ public String getClient() {

@Override
public void addDataToBundle(Map<String, Object> bundle) throws GeneratorException {
Object clientPrefix = bundle.get("clientPrefix");
String clientPrefix = (String) bundle.get("clientPrefix");
bundle.put("clientName", Helpers.toPascalCase(this.client));

if (clientPrefix.equals("query-suggestions")) {
bundle.put("clientPrefix", "suggestions");
} else {
bundle.put("clientPrefix", Helpers.camelize(clientPrefix));
}

bundle.put("clientImport", clientPrefix);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ public void addDataToBundle(Map<String, Object> bundle) throws GeneratorExceptio
bundle.put("clientName", "compositionClient");
bundle.put("importPackage", "@algolia/client-composition");
break;
case "realtime-personalization":
bundle.put("clientName", "realtimePersonalizationClient");
bundle.put("importPackage", "@algolia/client-realtime-personalization");
break;
case "algoliasearch":
bundle.put("clientName", "liteClient");
bundle.put("importPackage", "algoliasearch/lite");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,15 @@ public void run(Map<String, CodegenModel> models, Map<String, CodegenOperation>
List<Object> blocksE2E = new ArrayList<>();
ParametersWithDataType paramsType = new ParametersWithDataType(models, language, client, false);

bundle.put("e2eAppID", client.startsWith("composition") ? "METIS_APPLICATION_ID" : "ALGOLIA_APPLICATION_ID");
bundle.put(
"e2eAppID",
client.startsWith("composition") || client.startsWith("realtime") ? "METIS_APPLICATION_ID" : "ALGOLIA_APPLICATION_ID"
);
bundle.put(
"e2eApiKey",
client.startsWith("composition") ? "METIS_API_KEY" : (client.equals("monitoring") ? "MONITORING_API_KEY" : "ALGOLIA_ADMIN_KEY")
client.startsWith("composition") || client.startsWith("realtime")
? "METIS_API_KEY"
: (client.equals("monitoring") ? "MONITORING_API_KEY" : "ALGOLIA_ADMIN_KEY")
);
bundle.put("useEchoRequester", true);

Expand Down
1 change: 1 addition & 0 deletions playground/javascript/node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@algolia/client-insights": "link:../../../clients/algoliasearch-client-javascript/packages/client-insights",
"@algolia/client-personalization": "link:../../../clients/algoliasearch-client-javascript/packages/client-personalization",
"@algolia/client-query-suggestions": "link:../../../clients/algoliasearch-client-javascript/packages/client-query-suggestions",
"@algolia/client-realtime-personalization": "link:../../../clients/algoliasearch-client-javascript/packages/client-realtime-personalization",
"@algolia/client-search": "link:../../../clients/algoliasearch-client-javascript/packages/client-search",
"@algolia/composition": "link:../../../clients/algoliasearch-client-javascript/packages/composition",
"@algolia/ingestion": "link:../../../clients/algoliasearch-client-javascript/packages/ingestion",
Expand Down
27 changes: 27 additions & 0 deletions playground/javascript/node/realtimePersonalization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ApiError } from '@algolia/client-common';
import { realtimePersonalizationClient } from '@algolia/client-realtime-personalization';

const appId = process.env.METIS_APPLICATION_ID || '**** APP_ID *****';
const apiKey = process.env.METIS_API_KEY || '**** ADMIN_KEY *****';

// Init client with appId and apiKey
const client = realtimePersonalizationClient(appId, apiKey, 'us');

async function testRealtimePersonalization() {
try {
console.log(appId, apiKey);

const resp = await client.getUser({userToken: "foo"});

console.log(resp);

} catch (e) {
if (e instanceof ApiError) {
return console.log(`[${e.status}] ${e.message}`, e.stackTrace, e);
}

console.log('[ERROR]', e);
}
}

testRealtimePersonalization();
6 changes: 6 additions & 0 deletions specs/realtime-personalization/common/enums.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
strategy:
type: string
enum:
- session
- hybrid
example: 'session'
8 changes: 8 additions & 0 deletions specs/realtime-personalization/common/parameters.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# path
UserToken:
name: userToken
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We call this userID in all the personalization APIs.

in: path
required: true
description: Unique identifier representing a user for which to fetch the personalization profile.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
description: Unique identifier representing a user for which to fetch the personalization profile.
description: Unique identifier used to retrieve the personalization profile of a specific user.

schema:
$ref: '../../common/schemas/SearchParams.yml#/userToken'
44 changes: 44 additions & 0 deletions specs/realtime-personalization/common/schemas/user.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
user:
type: object
additionalProperties: false
required:
- version
- userID
- search
properties:
version:
type: string
description: Version of the response format.
userID:
type: string
description: User ID of the user.
search:
type: object
description: |
Index personalization filters by index name.
additionalProperties:
$ref: '#/searchFilters'

searchFilters:
type: object
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this allow us to store search configurations for multiple groups of indices, and have a dynamic name?

Example extracted from Francois' presentation:
Screenshot 2025-03-19 at 17 19 59

Not sure how we represent that in the spec though.

Copy link
Contributor Author

@benamib benamib Mar 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The attribute additionalProperties allows us to tell the generator that the key of the object can be dynamic but that the format of the value should be of type searchFilters.

I'm not fond of this method but we don't have too much choice to respect the response we want.
We could still challenge the response format to replace the objects by arrays which would make the specs clearer imo like:

...
"search": [
	{ "aliases": "abc", "strategy": "def", "filters": {...} }
]
...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the current specs the generated exemples looks like this :

{
  "version": "string",
  "userID": "string",
  "search": {
    "additionalProp1": {
      "indices": [
        "storefront",
        "storefront_price_asc",
        "storefront_price_desc"
      ],
      "strategy": "session",
      "filters": {
        "session": [
          "brand:Dyson<score=12>"
        ],
        "additionalProp1": {}
      },
      "additionalProp1": {}
    },
    "additionalProp2": {
      "indices": [
        "storefront",
        "storefront_price_asc",
        "storefront_price_desc"
      ],
      "strategy": "session",
      "filters": {
        "session": [
          "brand:Dyson<score=12>"
        ],
        "additionalProp1": {}
      },
      "additionalProp1": {}
    },
    "additionalProp3": {
      "indices": [
        "storefront",
        "storefront_price_asc",
        "storefront_price_desc"
      ],
      "strategy": "session",
      "filters": {
        "session": [
          "brand:Dyson<score=12>"
        ],
        "additionalProp1": {}
      },
      "additionalProp1": {}
    }
  },
  "additionalProp1": {}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the reason for this structure was so that the FE could retrieve the required attributes as quick as possible (it's more time consuming to filter an array based on a key than it is to get a specific key from a map).

This looks good for now, but if it causes issues we can rethink it

additionalProperties: false
properties:
indices:
type: array
items:
type: string
example: ['storefront', 'storefront_price_asc', 'storefront_price_desc']
strategy:
$ref: '../enums.yml#/strategy'
filters:
$ref: '#/searchFilter'

searchFilter:
type: object
additionalProperties: false
properties:
session:
type: array
items:
type: string
example: ['brand:Dyson<score=12>']
Loading