@algolia/wizard 0.5.0 → 0.6.0-rc.51.23

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.
@@ -1,36 +1,64 @@
1
- # Algolia JS SDK reference (search)
1
+ # Algolia SDK reference
2
2
 
3
- Authoritative reference for wiring **Algolia search** in JavaScript/TypeScript apps.
4
- Prefer the method shapes documented here over prior knowledge — they are pinned to
5
- the current stable majors and avoid known pitfalls (e.g. v5 type mismatches with
6
- InstantSearch).
3
+ Authoritative reference for the code the wizard generates. Prefer the method shapes
4
+ documented here over prior knowledge — they are pinned to the current stable majors
5
+ and avoid known pitfalls (e.g. v5 type mismatches with InstantSearch).
7
6
 
8
- ## Versions
7
+ Docs are loaded by exact name (`<name>-<key>.md`), keyed off the language registry in
8
+ `src/lib/languages.ts` and the framework registry in `src/lib/frameworks.ts`. Adding a
9
+ language means adding its `save-records-<key>.md`, or the doc-coverage test fails.
9
10
 
10
- - `algoliasearch` v5 (install `^5`)
11
- - `react-instantsearch` v7 (install `^7`)
12
- - `vue-instantsearch` v4 (install `^4`)
13
- - `instantsearch.js` v4 (install `^4`) — also the recommended choice for Angular
14
- - `angular-instantsearch` — DEPRECATED/archived (Sep 2024); do not use, prefer `instantsearch.js`
11
+ ## Ingestion clients
15
12
 
16
- Always install the **latest stable** within the major (use a caret range like `^5` /
17
- `^7`); never pin an exact patch.
13
+ One `save-records-<key>.md` per language Algolia ships an official API client for.
14
+ Install the latest stable within the major; never pin an exact patch.
15
+
16
+ | Language | Package | Version |
17
+ | --- | --- | --- |
18
+ | JavaScript/TypeScript | `algoliasearch` | `^5` |
19
+ | Python | `algoliasearch` | `>=4,<5` |
20
+ | Ruby | `algolia` | `~> 3.0` |
21
+ | PHP | `algolia/algoliasearch-client-php` | `^4` |
22
+ | Go | `github.com/algolia/algoliasearch-client-go/v4` | `v4` |
23
+ | Java | `com.algolia:algoliasearch` | `4.+` |
24
+ | Kotlin | `com.algolia:algoliasearch-client-kotlin` | `3.+` |
25
+ | Scala | `com.algolia:algoliasearch-scala_2.13` | `2.+` |
26
+ | C# | `Algolia.Search` | `7.*` |
27
+ | Swift | `algoliasearch-client-swift` | `9.0.0` |
28
+ | Dart | `algolia_client_search` | `^1.0.0` |
29
+
30
+ ## Search UI
31
+
32
+ | Strategy | Doc | Packages |
33
+ | --- | --- | --- |
34
+ | React (incl. Next.js) | `instantsearch-setup-react.md` | `react-instantsearch` v7 |
35
+ | Vue (incl. Nuxt) | `instantsearch-setup-vue.md` | `vue-instantsearch` v4 |
36
+ | Angular | `instantsearch-setup-angular.md` | `instantsearch.js` v4 |
37
+ | Plain JavaScript | `instantsearch-setup-js.md` | `instantsearch.js` v4 |
38
+ | Server-rendered templates | `instantsearch-setup-templates.md` | CDN script tags, no bundler |
39
+
40
+ `angular-instantsearch` is DEPRECATED/archived (Sep 2024); use `instantsearch.js`.
41
+
42
+ Server-rendered templates cover Algolia's official framework integrations — Rails,
43
+ Django, Laravel, Symfony — and any backend with no JavaScript build.
18
44
 
19
45
  ## Golden rules
20
46
 
47
+ - Ingestion is a **write** op: it needs a write key and must stay server-side. Read the
48
+ credentials from the `ALGOLIA_APPLICATION_ID` and `ALGOLIA_WRITE_API_KEY` env vars.
21
49
  - Use a **search-only API key** in any browser/client code. It is safe to expose.
22
- NEVER ship an admin or any write-capable key to the client.
23
- - Read the Application ID and search-only key from **public env vars**, never hardcode
24
- them inline.
50
+ NEVER ship an admin or any write-capable key to the client, and never render one into
51
+ HTML.
52
+ - Read the Application ID and search-only key from env vars or server-side config, never
53
+ hardcode them inline.
25
54
  - Instantiate the search client **once, outside your components**, and pass a stable
26
- reference. Do not inline `algoliasearch(...)` as a prop value — it breaks the
27
- client cache and causes re-renders.
55
+ reference. Do not inline `algoliasearch(...)` as a prop value — it breaks the client
56
+ cache and causes re-renders.
28
57
  - For InstantSearch, import the client from `algoliasearch/lite` (smaller bundle and
29
- correct types — see `instantsearch-setup.md`).
58
+ correct types).
59
+ - Every record needs an `objectID`. `saveObjects` auto-batches in groups of 1,000.
30
60
 
31
- ## Files
61
+ ## Other files
32
62
 
33
- - `instantsearch-setup.md` — framework-specific InstantSearch wiring (React, Vue,
34
- Angular, vanilla). Use this for the in-app search UI.
35
63
  - `search-single-index.md` — direct/manual search via the core client
36
64
  (`searchSingleIndex`), for cases where InstantSearch is not used.
@@ -0,0 +1,92 @@
1
+ # InstantSearch setup (CDN, server-rendered templates)
2
+
3
+ Use this when HTML is rendered server-side and there is no bundler or npm build
4
+ step: Rails ERB, Django templates, Laravel Blade, Symfony Twig.
5
+
6
+ ## CDN tags (shared layout)
7
+
8
+ ```html
9
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/instantsearch.css@8/themes/satellite-min.css">
10
+ <script src="https://cdn.jsdelivr.net/npm/algoliasearch@5/dist/lite/builds/browser.umd.js"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/instantsearch.js@4/dist/instantsearch.production.min.js"></script>
12
+ ```
13
+
14
+ Globals these expose:
15
+
16
+ - `window['algoliasearch/lite']` → `{ liteClient }`. The lite UMD build's global is
17
+ the literal string key `algoliasearch/lite`, **not** `algoliasearch.liteClient`.
18
+ (`dist/algoliasearch.umd.js` is the full write-capable client under
19
+ `window.algoliasearch` — don't use it for search UI.)
20
+ - `window.instantsearch` → the callable factory, with `.widgets`, `.connectors`.
21
+
22
+ Floating `@5`/`@4`/`@8` track the newest patch. Adding SRI `integrity` requires
23
+ exact pins instead (Algolia's install page publishes hashes for
24
+ `algoliasearch@5.56.0`, `instantsearch.js@4.108.0`, `instantsearch.css@8.18.0`).
25
+
26
+ ## Containers
27
+
28
+ ```html
29
+ <div id="search"
30
+ data-algolia-app-id="APP_ID"
31
+ data-algolia-search-key="SEARCH_ONLY_KEY"
32
+ data-algolia-index="products">
33
+ <div id="searchbox"></div>
34
+ <div id="hits"></div>
35
+ </div>
36
+ ```
37
+
38
+ ## Init
39
+
40
+ Inline `<script>` must run after both CDN scripts — put it at the end of `<body>`,
41
+ not in `<head>`.
42
+
43
+ ```html
44
+ <script>
45
+ const el = document.getElementById('search')
46
+ const { liteClient: algoliasearch } = window['algoliasearch/lite']
47
+ const searchClient = algoliasearch(
48
+ el.dataset.algoliaAppId,
49
+ el.dataset.algoliaSearchKey
50
+ )
51
+
52
+ const search = instantsearch({ indexName: el.dataset.algoliaIndex, searchClient })
53
+
54
+ search.addWidgets([
55
+ instantsearch.widgets.searchBox({ container: '#searchbox' }),
56
+ instantsearch.widgets.hits({
57
+ container: '#hits',
58
+ templates: {
59
+ item(hit, { html, components }) {
60
+ return html`
61
+ <article>
62
+ <h3>${components.Highlight({ attribute: 'name', hit })}</h3>
63
+ <p>${hit.description}</p>
64
+ </article>
65
+ `
66
+ },
67
+ },
68
+ }),
69
+ ])
70
+
71
+ search.start()
72
+ </script>
73
+ ```
74
+
75
+ `html` arrives as a property of the template function's second argument — it is
76
+ not an import and not a global. It escapes interpolated values, so it is the XSS
77
+ guard: never string-concatenate hit values or assign them to `innerHTML`.
78
+
79
+ ## Injecting credentials from server config
80
+
81
+ Render App ID and the search-only key into `data-` attributes (every engine below
82
+ escapes attribute output), then read them from `dataset` — attribute dashes become
83
+ camelCase: `data-algolia-app-id` → `el.dataset.algoliaAppId`. Never interpolate a
84
+ credential into a JS string literal.
85
+
86
+ - ERB: `data-algolia-app-id="<%= Rails.application.credentials.algolia[:app_id] %>"`
87
+ - Django: `data-algolia-app-id="{{ algolia_app_id }}"` (from the view context)
88
+ - Blade: `data-algolia-app-id="{{ config('services.algolia.app_id') }}"`
89
+ - Twig: `data-algolia-app-id="{{ algolia_app_id }}"` (from a Twig global/parameter)
90
+
91
+ Only ever a **search-only** API key client-side. A write or admin key must never
92
+ appear in HTML, a `data-` attribute, or inline JS.
@@ -0,0 +1,71 @@
1
+ # Save records (Algolia.Search v7)
2
+
3
+ Adds records to an index. Write op — use a **write key**, server-side only.
4
+
5
+ The generated script is a self-contained console project at
6
+ `.algolia-wizard/ingest/ingest.csproj` + `Program.cs`, run with
7
+ `dotnet run --project .algolia-wizard/ingest/ingest.csproj`.
8
+
9
+ ```xml
10
+ <Project Sdk="Microsoft.NET.Sdk">
11
+ <PropertyGroup>
12
+ <OutputType>Exe</OutputType>
13
+ <TargetFramework>net8.0</TargetFramework>
14
+ <ImplicitUsings>enable</ImplicitUsings>
15
+ <Nullable>enable</Nullable>
16
+ </PropertyGroup>
17
+ <ItemGroup>
18
+ <PackageReference Include="Algolia.Search" Version="7.*" />
19
+ </ItemGroup>
20
+ </Project>
21
+ ```
22
+
23
+ `<OutputType>Exe</OutputType>` is required — `dotnet run` on a library has no entry
24
+ point. `<TargetFramework>` must match an installed SDK; `net8.0` (LTS) is the safe
25
+ default. Under Central Package Management (`Directory.Packages.props` at the repo
26
+ root) omit `Version` here and pin it there — `Version` on a `PackageReference` is
27
+ an error under CPM (NU1008).
28
+
29
+ ```csharp
30
+ using System.Text.Json;
31
+ using Algolia.Search.Clients;
32
+
33
+ var appId = Environment.GetEnvironmentVariable("ALGOLIA_APPLICATION_ID")
34
+ ?? throw new InvalidOperationException("ALGOLIA_APPLICATION_ID is not set");
35
+ var writeKey = Environment.GetEnvironmentVariable("ALGOLIA_WRITE_API_KEY")
36
+ ?? throw new InvalidOperationException("ALGOLIA_WRITE_API_KEY is not set");
37
+
38
+ var client = new SearchClient(new SearchConfig(appId, writeKey));
39
+ var indexName = "products";
40
+
41
+ var records = new List<Dictionary<string, object>>
42
+ {
43
+ new() { ["objectID"] = "1", ["name"] = "Adam" },
44
+ new() { ["objectID"] = "2", ["name"] = "Benoit" },
45
+ };
46
+
47
+ var responses = await client.SaveObjectsAsync(indexName, records, waitForTasks: true);
48
+ ```
49
+
50
+ ```csharp
51
+ Task<List<BatchResponse>> SaveObjectsAsync<T>(
52
+ string indexName, IEnumerable<T> objects, bool waitForTasks = false,
53
+ int batchSize = 1000, RequestOptions options = null,
54
+ CancellationToken cancellationToken = default) where T : class;
55
+ ```
56
+
57
+ Records need an `objectID`; batching in 1,000s is automatic. Returns
58
+ `List<BatchResponse>` — one entry per batch, each `{ TaskID, ObjectIDs }`, never a
59
+ single response. `T` is constrained to `class`, so use `Dictionary<string, object>`
60
+ or a record/class, never a struct.
61
+
62
+ Read records from a JSON file:
63
+
64
+ ```csharp
65
+ var json = await File.ReadAllTextAsync("records.json");
66
+ var records = JsonSerializer.Deserialize<List<Dictionary<string, object>>>(json)
67
+ ?? new List<Dictionary<string, object>>();
68
+ ```
69
+
70
+ After a successful ingest, print `ALGOLIA_WIZARD_RECORD_COUNT=<n>` as the last
71
+ stdout line; send all other logging to stderr so nothing follows it on stdout.
@@ -0,0 +1,74 @@
1
+ # Save records (algolia_client_search v1)
2
+
3
+ Adds records to an index. Write op — use a **write key**, server-side only.
4
+
5
+ Use the generated v2 client `algolia_client_search`, never the legacy community
6
+ `algolia` package. The script is run with `dart run .algolia-wizard/ingest.dart`.
7
+
8
+ ```yaml
9
+ dependencies:
10
+ algolia_client_search: ^1.0.0
11
+ ```
12
+
13
+ This client has **no `saveObjects` helper** (only `saveObjectsWithTransformation`,
14
+ which needs an Ingestion transformation) — use `batch` with `addObject` actions.
15
+
16
+ ```dart
17
+ import 'dart:convert';
18
+ import 'dart:io';
19
+ import 'dart:math';
20
+
21
+ import 'package:algolia_client_search/algolia_client_search.dart';
22
+
23
+ Future<void> main() async {
24
+ final appId = Platform.environment['ALGOLIA_APPLICATION_ID'];
25
+ final writeKey = Platform.environment['ALGOLIA_WRITE_API_KEY'];
26
+ if (appId == null || writeKey == null) {
27
+ stderr.writeln('missing Algolia credentials');
28
+ exit(1);
29
+ }
30
+
31
+ const indexName = 'products';
32
+ final client = SearchClient(appId: appId, apiKey: writeKey);
33
+
34
+ final records = <Map<String, dynamic>>[
35
+ {'objectID': '1', 'name': 'Adam'},
36
+ {'objectID': '2', 'name': 'Benoit'},
37
+ ];
38
+
39
+ try {
40
+ for (var i = 0; i < records.length; i += 1000) {
41
+ final chunk = records.sublist(i, min(i + 1000, records.length));
42
+ final response = await client.batch(
43
+ indexName: indexName,
44
+ batchWriteParams: BatchWriteParams(
45
+ requests: chunk
46
+ .map((r) => BatchRequest(action: Action.addObject, body: r))
47
+ .toList(),
48
+ ),
49
+ );
50
+ stderr.writeln('task ${response.taskID}');
51
+ }
52
+ print('ALGOLIA_WIZARD_RECORD_COUNT=${records.length}');
53
+ } finally {
54
+ client.dispose();
55
+ }
56
+ }
57
+ ```
58
+
59
+ `batch` returns a **single** `BatchResponse { int taskID, List<String> objectIDs }`,
60
+ not a list. Gotchas: every record needs an `objectID`; there is no auto-chunking,
61
+ so cap each call at 1,000 records yourself; use the `Action.addObject` enum value
62
+ (`Action.updateObject` replaces instead of adds); and `client.dispose()` is
63
+ mandatory — without it `dart run` never exits.
64
+
65
+ Read records from a JSON file:
66
+
67
+ ```dart
68
+ final json = await File('records.json').readAsString();
69
+ final records = (jsonDecode(json) as List).cast<Map<String, dynamic>>();
70
+ ```
71
+
72
+ After a successful ingest, print `ALGOLIA_WIZARD_RECORD_COUNT=<n>` as the last
73
+ stdout line (before `dispose()` is fine); send all other logging to stderr so
74
+ nothing follows it on stdout.
@@ -0,0 +1,62 @@
1
+ # Save records (algoliasearch-client-go v4)
2
+
3
+ Adds records to an index. Write op — use a **write key**, server-side only.
4
+
5
+ Imports are the dependency declaration; `go mod tidy` resolves
6
+ `github.com/algolia/algoliasearch-client-go/v4`. Standalone `package main` file,
7
+ run with `go run`. Nothing panics on its own — every call returns `(value, error)`.
8
+
9
+ ```go
10
+ package main
11
+
12
+ import (
13
+ "fmt"
14
+ "os"
15
+
16
+ "github.com/algolia/algoliasearch-client-go/v4/algolia/search"
17
+ )
18
+
19
+ func main() {
20
+ client, err := search.NewClient(os.Getenv("ALGOLIA_APPLICATION_ID"), os.Getenv("ALGOLIA_WRITE_API_KEY"))
21
+ if err != nil {
22
+ panic(err) // NewClient fails on an empty/invalid app ID or key
23
+ }
24
+
25
+ indexName := "products"
26
+ records := []map[string]any{{"objectID": "1", "name": "Hot 100"}}
27
+
28
+ if _, err := client.SaveObjects(indexName, records, search.WithWaitForTasks(true)); err != nil {
29
+ panic(err)
30
+ }
31
+
32
+ fmt.Printf("ALGOLIA_WIZARD_RECORD_COUNT=%d\n", len(records))
33
+ }
34
+ ```
35
+
36
+ ```go
37
+ func (c *APIClient) SaveObjects(
38
+ indexName string,
39
+ objects []map[string]any, // maps only, each with an "objectID"
40
+ opts ...ChunkedBatchOption, // search.WithWaitForTasks(bool), search.WithBatchSize(int) (default 1000)
41
+ ) ([]BatchResponse, error) // BatchResponse{ TaskID int64; ObjectIDs []string }
42
+ ```
43
+
44
+ One `BatchResponse` per batch of 1,000, not per record — `len(res)` is the batch count.
45
+
46
+ Read records from a JSON file:
47
+
48
+ ```go
49
+ import "encoding/json"
50
+
51
+ content, err := os.ReadFile("records.json")
52
+ if err != nil {
53
+ panic(err)
54
+ }
55
+ var records []map[string]any
56
+ if err := json.Unmarshal(content, &records); err != nil {
57
+ panic(err)
58
+ }
59
+ ```
60
+
61
+ After a successful ingest print `ALGOLIA_WIZARD_RECORD_COUNT=<n>` (n = records
62
+ read, not `len(res)`) as the last stdout line, trailing newline included.
@@ -0,0 +1,66 @@
1
+ # Save records (algoliasearch Java v4)
2
+
3
+ Adds records to an index. Write op — use a **write key**, server-side only.
4
+
5
+ ```xml
6
+ <dependency>
7
+ <groupId>com.algolia</groupId>
8
+ <artifactId>algoliasearch</artifactId>
9
+ <version>4.20.0</version>
10
+ </dependency>
11
+ ```
12
+
13
+ ```groovy
14
+ implementation 'com.algolia:algoliasearch:4.20.0'
15
+ ```
16
+
17
+ The class MUST be `AlgoliaWizardIngest` with a `public static void main`, in the
18
+ **default package** (no `package` statement) — the wizard runs a fixed main class.
19
+
20
+ ```java
21
+ import com.algolia.api.SearchClient;
22
+ import java.util.List;
23
+ import java.util.Map;
24
+
25
+ public class AlgoliaWizardIngest {
26
+
27
+ public static void main(String[] args) throws Exception {
28
+ String indexName = "products";
29
+ List<Map<String, Object>> records = List.of(Map.of("objectID", "1", "name", "Hot 100"));
30
+
31
+ SearchClient client =
32
+ new SearchClient(System.getenv("ALGOLIA_APPLICATION_ID"), System.getenv("ALGOLIA_WRITE_API_KEY"));
33
+ try {
34
+ client.saveObjects(indexName, records);
35
+ System.out.printf("ALGOLIA_WIZARD_RECORD_COUNT=%d%n", records.size());
36
+ } finally {
37
+ client.close(); // required: okhttp's non-daemon threads keep the JVM alive otherwise
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ Blocking API — calls return directly and throw `AlgoliaApiException` on failure.
44
+
45
+ ```java
46
+ // objects: Maps or POJOs (Jackson-serialized), each with an objectID
47
+ <T> List<BatchResponse> saveObjects(String indexName, Iterable<T> objects)
48
+ <T> List<BatchResponse> saveObjects(String indexName, Iterable<T> objects,
49
+ boolean waitForTasks, int batchSize, RequestOptions requestOptions) // batchSize default 1000
50
+ ```
51
+
52
+ One `BatchResponse` (`getTaskID()`, `getObjectIDs()`) per batch of 1,000, not per record.
53
+
54
+ Read records from a JSON file (Jackson is a transitive dependency):
55
+
56
+ ```java
57
+ import com.fasterxml.jackson.core.type.TypeReference;
58
+ import com.fasterxml.jackson.databind.ObjectMapper;
59
+ import java.io.File;
60
+
61
+ List<Map<String, Object>> records = new ObjectMapper()
62
+ .readValue(new File("records.json"), new TypeReference<List<Map<String, Object>>>() {});
63
+ ```
64
+
65
+ After a successful ingest print `ALGOLIA_WIZARD_RECORD_COUNT=<n>` (n = records
66
+ read, not the returned list size) as the last stdout line.
@@ -0,0 +1,60 @@
1
+ # Save records (algoliasearch-client-kotlin v3)
2
+
3
+ Adds records to an index. Write op — use a **write key**, server-side only.
4
+
5
+ Gradle Kotlin DSL. The client ships only `ktor-client-core`, so a JVM engine must
6
+ be declared too or the first call fails with no HTTP engine found:
7
+
8
+ ```kotlin
9
+ implementation("com.algolia:algoliasearch-client-kotlin:3.23.0")
10
+ implementation("io.ktor:ktor-client-okhttp:3.2.0")
11
+ ```
12
+
13
+ `saveObjects` is a `suspend` extension, so a standalone script wraps it in
14
+ `runBlocking`. The object MUST be `AlgoliaWizardIngest` in the **default package**
15
+ (no `package` statement), and `@JvmStatic` is required — a `fun main` inside an
16
+ object is not a JVM entrypoint without it.
17
+
18
+ ```kotlin
19
+ import com.algolia.client.api.SearchClient
20
+ import com.algolia.client.extensions.*
21
+ import java.io.File
22
+ import kotlinx.coroutines.runBlocking
23
+ import kotlinx.serialization.json.*
24
+
25
+ object AlgoliaWizardIngest {
26
+ @JvmStatic
27
+ fun main(args: Array<String>): Unit = runBlocking {
28
+ val indexName = "products"
29
+ val records: List<JsonObject> = listOf(buildJsonObject { put("objectID", "1"); put("name", "Hot 100") })
30
+
31
+ // System.getenv returns String?; the constructor params are non-null
32
+ SearchClient(
33
+ appId = requireNotNull(System.getenv("ALGOLIA_APPLICATION_ID")),
34
+ apiKey = requireNotNull(System.getenv("ALGOLIA_WRITE_API_KEY")),
35
+ ).use { client -> // AutoCloseable: without use/close, okhttp's non-daemon threads hang the JVM
36
+ client.saveObjects(indexName = indexName, objects = records)
37
+ println("ALGOLIA_WIZARD_RECORD_COUNT=${records.size}")
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ ```kotlin
44
+ // objects is List<JsonObject> — not arbitrary data classes; each needs an objectID
45
+ suspend fun SearchClient.saveObjects(
46
+ indexName: String, objects: List<JsonObject>, waitForTasks: Boolean = false,
47
+ batchSize: Int = 1000, requestOptions: RequestOptions? = null,
48
+ ): List<BatchResponse>
49
+ ```
50
+
51
+ One `BatchResponse` (`taskID`, `objectIDs`) per batch of 1,000, not per record.
52
+
53
+ Read records from a JSON file (no serialization plugin needed for `JsonObject`):
54
+
55
+ ```kotlin
56
+ val records = Json.parseToJsonElement(File("records.json").readText()).jsonArray.map { it.jsonObject }
57
+ ```
58
+
59
+ After a successful ingest print `ALGOLIA_WIZARD_RECORD_COUNT=<n>` (n = records
60
+ read, not the returned list size) as the last stdout line.
@@ -0,0 +1,50 @@
1
+ # Save records (algoliasearch-client-php v4)
2
+
3
+ Adds records to an index. Write op — use a **write key**, server-side only.
4
+
5
+ Dependency: `algolia/algoliasearch-client-php` `^4` — the wizard runs
6
+ `composer require` for you; do not hand-edit `composer.json`.
7
+
8
+ `require 'vendor/autoload.php'` before anything else, or the class won't resolve
9
+ (from `.algolia-wizard/` the path is `__DIR__.'/../vendor/autoload.php'`).
10
+
11
+ ```php
12
+ <?php
13
+
14
+ require __DIR__.'/../vendor/autoload.php';
15
+
16
+ use Algolia\AlgoliaSearch\Api\SearchClient;
17
+
18
+ $appId = getenv('ALGOLIA_APPLICATION_ID');
19
+ $apiKey = getenv('ALGOLIA_WRITE_API_KEY');
20
+ $indexName = 'playlists';
21
+
22
+ $client = SearchClient::create($appId, $apiKey);
23
+ $records = [['objectID' => '1', 'name' => 'Hot 100', 'visibility' => 'public']];
24
+
25
+ $responses = $client->saveObjects($indexName, $records, true);
26
+ ```
27
+
28
+ ```php
29
+ saveObjects(
30
+ string $indexName, // required
31
+ array $objects, // required
32
+ bool $waitForTasks = false,
33
+ int $batchSize = 1000,
34
+ array $requestOptions = []
35
+ ) // => array of batch responses, one per 1,000-record batch
36
+ ```
37
+
38
+ Unlike the typed clients, PHP returns **plain associative arrays**, not objects:
39
+ `$responses[0]['taskID']`, `$responses[0]['objectIDs']`. Always an array of them —
40
+ never treat it as one response.
41
+
42
+ Read records from a JSON file:
43
+
44
+ ```php
45
+ $records = json_decode(file_get_contents($recordsPath), true);
46
+ ```
47
+
48
+ After a successful ingest, print `ALGOLIA_WIZARD_RECORD_COUNT=<n>` as the last
49
+ stdout line (`echo 'ALGOLIA_WIZARD_RECORD_COUNT='.count($records)."\n";`), with no
50
+ other output after it.
@@ -0,0 +1,51 @@
1
+ # Save records (algoliasearch v4)
2
+
3
+ Adds records to an index. Write op — use a **write key**, server-side only.
4
+
5
+ Dependency: `algoliasearch>=4,<5` (PyPI).
6
+
7
+ v4 is async-first: `SearchClient` is coroutine-based (every method needs `await`).
8
+ **`SearchClientSync` is the sync twin** — use it in a standalone script, via `with`
9
+ so the transport closes.
10
+
11
+ ```python
12
+ import os
13
+
14
+ from algoliasearch.search.client import SearchClientSync
15
+
16
+ app_id = os.environ["ALGOLIA_APPLICATION_ID"]
17
+ api_key = os.environ["ALGOLIA_WRITE_API_KEY"]
18
+ index_name = "playlists"
19
+ records = [{"objectID": "1", "name": "Hot 100", "visibility": "public"}]
20
+
21
+ with SearchClientSync(app_id, api_key) as client:
22
+ responses = client.save_objects(
23
+ index_name=index_name,
24
+ objects=records,
25
+ wait_for_tasks=True,
26
+ )
27
+ ```
28
+
29
+ ```python
30
+ save_objects(
31
+ index_name: str, # required
32
+ objects: Iterable[Dict[str, Any]], # required
33
+ wait_for_tasks: bool = False,
34
+ batch_size: int = 1000,
35
+ ) -> List[BatchResponse] # one entry per 1,000-record batch
36
+ ```
37
+
38
+ `BatchResponse` attributes are **snake_case**: `r.task_id`, `r.object_ids` (the
39
+ wire keys are `taskID`/`objectIDs`). It is a list — never treat it as one object.
40
+
41
+ Read records from a JSON file:
42
+
43
+ ```python
44
+ import json
45
+
46
+ with open(records_path, encoding="utf-8") as f:
47
+ records = json.load(f)
48
+ ```
49
+
50
+ After a successful ingest, print `ALGOLIA_WIZARD_RECORD_COUNT=<n>` as the last
51
+ stdout line.
@@ -0,0 +1,48 @@
1
+ # Save records (algolia v3)
2
+
3
+ Adds records to an index. Write op — use a **write key**, server-side only.
4
+
5
+ Dependency: gem `algolia`, `~> 3.0` (`gem 'algolia', '~> 3.0'`).
6
+
7
+ `require "algolia"` (gem name is `algolia`, not `algoliasearch`). Build the client
8
+ with `Algolia::SearchClient.create`.
9
+
10
+ ```ruby
11
+ require "algolia"
12
+
13
+ app_id = ENV.fetch("ALGOLIA_APPLICATION_ID")
14
+ api_key = ENV.fetch("ALGOLIA_WRITE_API_KEY")
15
+ index_name = "playlists"
16
+
17
+ client = Algolia::SearchClient.create(app_id, api_key)
18
+
19
+ records = [{ "objectID" => "1", "name" => "Hot 100", "visibility" => "public" }]
20
+
21
+ responses = client.save_objects(index_name, records, true)
22
+ ```
23
+
24
+ ```ruby
25
+ save_objects(
26
+ index_name, # String, required
27
+ objects, # Array<Hash>, required
28
+ wait_for_tasks = false,
29
+ batch_size = 1000,
30
+ request_options = {}
31
+ ) # => Array<Algolia::Search::BatchResponse>, one entry per 1,000-record batch
32
+ ```
33
+
34
+ Args are **positional**, and every method/field is **snake_case**: read
35
+ `r.task_id` and `r.object_ids`, not `taskID`/`objectIDs`. The return value is an
36
+ array — never treat it as one object.
37
+
38
+ Read records from a JSON file:
39
+
40
+ ```ruby
41
+ require "json"
42
+
43
+ records = JSON.parse(File.read(records_path))
44
+ ```
45
+
46
+ After a successful ingest, print `ALGOLIA_WIZARD_RECORD_COUNT=<n>` as the last
47
+ stdout line (`puts "ALGOLIA_WIZARD_RECORD_COUNT=#{records.length}"`), with no
48
+ other output after it.