algolia-codegen 0.1.2 → 0.1.3

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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 nightlightmare
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md CHANGED
@@ -1,12 +1,20 @@
1
1
  # Algolia Type Generator
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/algolia-codegen.svg?style=flat-square)](https://www.npmjs.com/package/algolia-codegen)
4
+ [![npm downloads](https://img.shields.io/npm/dm/algolia-codegen.svg?style=flat-square)](https://www.npmjs.com/package/algolia-codegen)
5
+ [![npm bundle size](https://img.shields.io/bundlephobia/minzip/algolia-codegen?style=flat-square)](https://bundlephobia.com/package/algolia-codegen)
6
+ [![GitHub stars](https://img.shields.io/github/stars/nightlightmare/algolia-codegen.svg?style=flat-square&logo=github)](https://github.com/nightlightmare/algolia-codegen)
7
+ [![GitHub issues](https://img.shields.io/github/issues/nightlightmare/algolia-codegen.svg?style=flat-square&logo=github)](https://github.com/nightlightmare/algolia-codegen/issues)
8
+ [![GitHub license](https://img.shields.io/github/license/nightlightmare/algolia-codegen.svg?style=flat-square)](https://github.com/nightlightmare/algolia-codegen/blob/main/LICENSE)
9
+ [![Node.js version](https://img.shields.io/node/v/algolia-codegen.svg?style=flat-square)](https://nodejs.org/)
10
+
3
11
  This script automatically generates TypeScript types from your Algolia indices by fetching sample records and analyzing their structure. It supports multiple indices and flexible configuration through a config file.
4
12
 
5
13
  **Repository**: [https://github.com/nightlightmare/algolia-codegen](https://github.com/nightlightmare/algolia-codegen)
6
14
 
7
15
  ## Prerequisites
8
16
 
9
- - Node.js >= 18
17
+ - Node.js >= 20
10
18
  - Algolia account with at least one index
11
19
 
12
20
  ## Installation
@@ -30,17 +38,17 @@ yarn add algolia-codegen
30
38
  1. Create a configuration file named `algolia-codegen.ts` (or `.js`) in your project root:
31
39
 
32
40
  ```typescript
33
- import type { AlgoliaCodegenConfig } from "algolia-codegen";
41
+ import type { AlgoliaCodegenConfig } from 'algolia-codegen';
34
42
 
35
43
  const config: AlgoliaCodegenConfig = {
36
44
  overwrite: true,
37
45
  generates: {
38
- "src/algolia/types.ts": {
39
- appId: "YOUR_APP_ID",
40
- searchKey: "YOUR_SEARCH_API_KEY",
41
- indexName: "products",
42
- prefix: "Algolia", // Optional
43
- postfix: "Type", // Optional
46
+ 'src/algolia/types.ts': {
47
+ appId: 'YOUR_APP_ID',
48
+ searchKey: 'YOUR_SEARCH_API_KEY',
49
+ indexName: 'products',
50
+ prefix: 'Algolia', // Optional
51
+ postfix: 'Type', // Optional
44
52
  },
45
53
  },
46
54
  };
@@ -65,17 +73,17 @@ npx algolia-codegen
65
73
  Create a configuration file named `algolia-codegen.ts` (or `.js`) in your project root. The config file should export a default object with the following structure:
66
74
 
67
75
  ```typescript
68
- import type { AlgoliaCodegenConfig } from "algolia-codegen";
76
+ import type { AlgoliaCodegenConfig } from 'algolia-codegen';
69
77
 
70
78
  const config: AlgoliaCodegenConfig = {
71
79
  overwrite: true,
72
80
  generates: {
73
- "src/algolia/types.ts": {
74
- appId: "YOUR_APP_ID",
75
- searchKey: "YOUR_SEARCH_API_KEY",
76
- indexName: "products",
77
- prefix: "Algolia", // Optional
78
- postfix: "Type", // Optional
81
+ 'src/algolia/types.ts': {
82
+ appId: 'YOUR_APP_ID',
83
+ searchKey: 'YOUR_SEARCH_API_KEY',
84
+ indexName: 'products',
85
+ prefix: 'Algolia', // Optional
86
+ postfix: 'Type', // Optional
79
87
  },
80
88
  },
81
89
  };
@@ -91,15 +99,15 @@ You can generate types for multiple indices:
91
99
  const config: AlgoliaCodegenConfig = {
92
100
  overwrite: true,
93
101
  generates: {
94
- "src/algolia/products.ts": {
95
- appId: "YOUR_APP_ID",
96
- searchKey: "YOUR_SEARCH_API_KEY",
97
- indexName: "products",
102
+ 'src/algolia/products.ts': {
103
+ appId: 'YOUR_APP_ID',
104
+ searchKey: 'YOUR_SEARCH_API_KEY',
105
+ indexName: 'products',
98
106
  },
99
- "src/algolia/users.ts": {
100
- appId: "YOUR_APP_ID",
101
- searchKey: "YOUR_SEARCH_API_KEY",
102
- indexName: "users",
107
+ 'src/algolia/users.ts': {
108
+ appId: 'YOUR_APP_ID',
109
+ searchKey: 'YOUR_SEARCH_API_KEY',
110
+ indexName: 'users',
103
111
  },
104
112
  },
105
113
  };
@@ -114,17 +122,17 @@ const config: AlgoliaCodegenConfig = {
114
122
  overwrite: true,
115
123
  generates: [
116
124
  {
117
- "src/algolia/products.ts": {
118
- appId: "YOUR_APP_ID",
119
- searchKey: "YOUR_SEARCH_API_KEY",
120
- indexName: "products",
125
+ 'src/algolia/products.ts': {
126
+ appId: 'YOUR_APP_ID',
127
+ searchKey: 'YOUR_SEARCH_API_KEY',
128
+ indexName: 'products',
121
129
  },
122
130
  },
123
131
  {
124
- "src/algolia/users.ts": {
125
- appId: "YOUR_APP_ID",
126
- searchKey: "YOUR_SEARCH_API_KEY",
127
- indexName: "users",
132
+ 'src/algolia/users.ts': {
133
+ appId: 'YOUR_APP_ID',
134
+ searchKey: 'YOUR_SEARCH_API_KEY',
135
+ indexName: 'users',
128
136
  },
129
137
  },
130
138
  ],
@@ -187,13 +195,13 @@ type AlgoliaCodegenGeneratorConfig = {
187
195
  - Fetches a sample record from the specified index
188
196
  - Analyzes the record structure and generates TypeScript types
189
197
  - Creates a single TypeScript file containing all types found in the index
190
- 3. **Type Generation**: The generator automatically:
198
+ 4. **Type Generation**: The generator automatically:
191
199
  - Infers types from the sample record structure
192
200
  - Handles nested objects, arrays, and complex types
193
201
  - Detects and generates generic `IdValue<T>` types for Algolia's id-value pattern arrays
194
202
  - Generates proper TypeScript interfaces with JSDoc comments
195
203
  - Sorts types by dependencies for correct ordering
196
- 4. **Error Handling**: Continues processing other files even if one fails, with detailed error messages
204
+ 5. **Error Handling**: Continues processing other files even if one fails, with detailed error messages
197
205
 
198
206
  Each generated file contains all types found in the index, including nested types, properly organized and sorted by dependencies.
199
207
 
@@ -213,44 +221,11 @@ Each generated file contains all types found in the index, including nested type
213
221
 
214
222
  ## Examples
215
223
 
216
- ### Generated Type Example
217
-
218
- Given an Algolia record like:
219
-
220
- ```json
221
- {
222
- "objectID": "123",
223
- "name": "Product Name",
224
- "price": 99.99,
225
- "tags": ["tag1", "tag2"],
226
- "metadata": {
227
- "category": "electronics",
228
- "rating": 4.5
229
- }
230
- }
231
- ```
232
-
233
- The generator will create TypeScript types:
234
-
235
- ```typescript
236
- /**
237
- * Generated TypeScript types for Algolia index: products
238
- * This file is auto-generated. Do not edit manually.
239
- */
240
-
241
- export interface AlgoliaHitType {
242
- metadata: AlgoliaMetadataType;
243
- name: string;
244
- objectID: string;
245
- price: number;
246
- tags: string[];
247
- }
248
-
249
- export interface AlgoliaMetadataType {
250
- category: string;
251
- rating: number;
252
- }
253
- ```
224
+ See the [examples directory](./examples/) for comprehensive examples including:
225
+ - Framework integrations (Next.js, React, Vue)
226
+ - CI/CD integration (GitHub Actions)
227
+ - Custom prefixes/postfixes
228
+ - Multiple indices configuration
254
229
 
255
230
  ## Repository
256
231
 
@@ -11,7 +11,7 @@ async function loadTypeScriptConfig(resolvedPath) {
11
11
  const result = esbuild.transformSync(source, {
12
12
  loader: "ts",
13
13
  format: "esm",
14
- target: "node18",
14
+ target: "node20",
15
15
  sourcefile: resolvedPath
16
16
  });
17
17
  if (!result.code) {
@@ -25,10 +25,8 @@ async function loadTypeScriptConfig(resolvedPath) {
25
25
  // src/utils/validations/generator-config.ts
26
26
  function validateGeneratorConfig(config, path) {
27
27
  if (typeof config !== "object" || config === null || Array.isArray(config)) {
28
- throw new Error(
29
- `Invalid generator config: must be an object
30
- Path: ${path}`
31
- );
28
+ throw new Error(`Invalid generator config: must be an object
29
+ Path: ${path}`);
32
30
  }
33
31
  const cfg = config;
34
32
  const requiredFields = [
@@ -70,10 +68,8 @@ Received: ${typeof cfg.postfix}`
70
68
  // src/utils/validations/url-schema.ts
71
69
  function validateUrlSchema(urlSchema, path) {
72
70
  if (typeof urlSchema !== "object" || urlSchema === null || Array.isArray(urlSchema)) {
73
- throw new Error(
74
- `Invalid generates entry: must be an object
75
- Path: ${path}`
76
- );
71
+ throw new Error(`Invalid generates entry: must be an object
72
+ Path: ${path}`);
77
73
  }
78
74
  const schema = urlSchema;
79
75
  for (const [filePath, generatorConfig] of Object.entries(schema)) {
@@ -90,10 +86,8 @@ Path: ${path}[${filePath}]`
90
86
  // src/utils/validations/config.ts
91
87
  function validateConfig(config, configPath) {
92
88
  if (typeof config !== "object" || config === null || Array.isArray(config)) {
93
- throw new Error(
94
- `Invalid config: must be an object
95
- Config file: ${configPath}`
96
- );
89
+ throw new Error(`Invalid config: must be an object
90
+ Config file: ${configPath}`);
97
91
  }
98
92
  const cfg = config;
99
93
  if (!("overwrite" in cfg)) {
@@ -174,10 +168,8 @@ Make sure esbuild is installed as a dependency.`
174
168
  configModule = await import(configUrl);
175
169
  } catch (importError) {
176
170
  const errorMessage = importError instanceof Error ? importError.message : String(importError);
177
- throw new Error(
178
- `Failed to import config file: ${resolvedPath}
179
- Error: ${errorMessage}`
180
- );
171
+ throw new Error(`Failed to import config file: ${resolvedPath}
172
+ Error: ${errorMessage}`);
181
173
  }
182
174
  }
183
175
  if (!configModule.default) {
@@ -306,7 +298,7 @@ var TypeGenerator = class {
306
298
  return `${this.prefix}Hit${this.postfix}`;
307
299
  }
308
300
  const lastPart = path[path.length - 1];
309
- const parts = lastPart.replace(/[\[\]]/g, "").split(/[-_\s]+/).filter(Boolean);
301
+ const parts = lastPart.replace(/[[\]]/g, "").split(/[-_\s]+/).filter(Boolean);
310
302
  const pascalCase = parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
311
303
  if (path.length > 1) {
312
304
  const parent = path[path.length - 2];
@@ -472,10 +464,7 @@ Set overwrite: true in config to allow overwriting existing files.`
472
464
  console.log(`App ID: ${generatorConfig.appId}`);
473
465
  let client;
474
466
  try {
475
- client = algoliasearch(
476
- generatorConfig.appId,
477
- generatorConfig.searchKey
478
- );
467
+ client = algoliasearch(generatorConfig.appId, generatorConfig.searchKey);
479
468
  } catch (error) {
480
469
  throw new Error(
481
470
  `Failed to initialize Algolia client: ${error instanceof Error ? error.message : String(error)}`
package/dist/cli.cjs CHANGED
@@ -42,7 +42,7 @@ async function loadTypeScriptConfig(resolvedPath) {
42
42
  const result = esbuild.transformSync(source, {
43
43
  loader: "ts",
44
44
  format: "esm",
45
- target: "node18",
45
+ target: "node20",
46
46
  sourcefile: resolvedPath
47
47
  });
48
48
  if (!result.code) {
@@ -56,10 +56,8 @@ async function loadTypeScriptConfig(resolvedPath) {
56
56
  // src/utils/validations/generator-config.ts
57
57
  function validateGeneratorConfig(config, path) {
58
58
  if (typeof config !== "object" || config === null || Array.isArray(config)) {
59
- throw new Error(
60
- `Invalid generator config: must be an object
61
- Path: ${path}`
62
- );
59
+ throw new Error(`Invalid generator config: must be an object
60
+ Path: ${path}`);
63
61
  }
64
62
  const cfg = config;
65
63
  const requiredFields = [
@@ -101,10 +99,8 @@ Received: ${typeof cfg.postfix}`
101
99
  // src/utils/validations/url-schema.ts
102
100
  function validateUrlSchema(urlSchema, path) {
103
101
  if (typeof urlSchema !== "object" || urlSchema === null || Array.isArray(urlSchema)) {
104
- throw new Error(
105
- `Invalid generates entry: must be an object
106
- Path: ${path}`
107
- );
102
+ throw new Error(`Invalid generates entry: must be an object
103
+ Path: ${path}`);
108
104
  }
109
105
  const schema = urlSchema;
110
106
  for (const [filePath, generatorConfig] of Object.entries(schema)) {
@@ -121,10 +117,8 @@ Path: ${path}[${filePath}]`
121
117
  // src/utils/validations/config.ts
122
118
  function validateConfig(config, configPath) {
123
119
  if (typeof config !== "object" || config === null || Array.isArray(config)) {
124
- throw new Error(
125
- `Invalid config: must be an object
126
- Config file: ${configPath}`
127
- );
120
+ throw new Error(`Invalid config: must be an object
121
+ Config file: ${configPath}`);
128
122
  }
129
123
  const cfg = config;
130
124
  if (!("overwrite" in cfg)) {
@@ -205,10 +199,8 @@ Make sure esbuild is installed as a dependency.`
205
199
  configModule = await import(configUrl);
206
200
  } catch (importError) {
207
201
  const errorMessage = importError instanceof Error ? importError.message : String(importError);
208
- throw new Error(
209
- `Failed to import config file: ${resolvedPath}
210
- Error: ${errorMessage}`
211
- );
202
+ throw new Error(`Failed to import config file: ${resolvedPath}
203
+ Error: ${errorMessage}`);
212
204
  }
213
205
  }
214
206
  if (!configModule.default) {
@@ -337,7 +329,7 @@ var TypeGenerator = class {
337
329
  return `${this.prefix}Hit${this.postfix}`;
338
330
  }
339
331
  const lastPart = path[path.length - 1];
340
- const parts = lastPart.replace(/[\[\]]/g, "").split(/[-_\s]+/).filter(Boolean);
332
+ const parts = lastPart.replace(/[[\]]/g, "").split(/[-_\s]+/).filter(Boolean);
341
333
  const pascalCase = parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
342
334
  if (path.length > 1) {
343
335
  const parent = path[path.length - 2];
@@ -503,10 +495,7 @@ Set overwrite: true in config to allow overwriting existing files.`
503
495
  console.log(`App ID: ${generatorConfig.appId}`);
504
496
  let client;
505
497
  try {
506
- client = (0, import_algoliasearch.algoliasearch)(
507
- generatorConfig.appId,
508
- generatorConfig.searchKey
509
- );
498
+ client = (0, import_algoliasearch.algoliasearch)(generatorConfig.appId, generatorConfig.searchKey);
510
499
  } catch (error) {
511
500
  throw new Error(
512
501
  `Failed to initialize Algolia client: ${error instanceof Error ? error.message : String(error)}`
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  main
4
- } from "./chunk-R66ECGLW.js";
4
+ } from "./chunk-4T5DZK75.js";
5
5
 
6
6
  // src/cli.ts
7
7
  import { config as loadDotenv } from "dotenv";
package/dist/index.cjs CHANGED
@@ -50,7 +50,7 @@ async function loadTypeScriptConfig(resolvedPath) {
50
50
  const result = esbuild.transformSync(source, {
51
51
  loader: "ts",
52
52
  format: "esm",
53
- target: "node18",
53
+ target: "node20",
54
54
  sourcefile: resolvedPath
55
55
  });
56
56
  if (!result.code) {
@@ -64,10 +64,8 @@ async function loadTypeScriptConfig(resolvedPath) {
64
64
  // src/utils/validations/generator-config.ts
65
65
  function validateGeneratorConfig(config, path) {
66
66
  if (typeof config !== "object" || config === null || Array.isArray(config)) {
67
- throw new Error(
68
- `Invalid generator config: must be an object
69
- Path: ${path}`
70
- );
67
+ throw new Error(`Invalid generator config: must be an object
68
+ Path: ${path}`);
71
69
  }
72
70
  const cfg = config;
73
71
  const requiredFields = [
@@ -109,10 +107,8 @@ Received: ${typeof cfg.postfix}`
109
107
  // src/utils/validations/url-schema.ts
110
108
  function validateUrlSchema(urlSchema, path) {
111
109
  if (typeof urlSchema !== "object" || urlSchema === null || Array.isArray(urlSchema)) {
112
- throw new Error(
113
- `Invalid generates entry: must be an object
114
- Path: ${path}`
115
- );
110
+ throw new Error(`Invalid generates entry: must be an object
111
+ Path: ${path}`);
116
112
  }
117
113
  const schema = urlSchema;
118
114
  for (const [filePath, generatorConfig] of Object.entries(schema)) {
@@ -129,10 +125,8 @@ Path: ${path}[${filePath}]`
129
125
  // src/utils/validations/config.ts
130
126
  function validateConfig(config, configPath) {
131
127
  if (typeof config !== "object" || config === null || Array.isArray(config)) {
132
- throw new Error(
133
- `Invalid config: must be an object
134
- Config file: ${configPath}`
135
- );
128
+ throw new Error(`Invalid config: must be an object
129
+ Config file: ${configPath}`);
136
130
  }
137
131
  const cfg = config;
138
132
  if (!("overwrite" in cfg)) {
@@ -213,10 +207,8 @@ Make sure esbuild is installed as a dependency.`
213
207
  configModule = await import(configUrl);
214
208
  } catch (importError) {
215
209
  const errorMessage = importError instanceof Error ? importError.message : String(importError);
216
- throw new Error(
217
- `Failed to import config file: ${resolvedPath}
218
- Error: ${errorMessage}`
219
- );
210
+ throw new Error(`Failed to import config file: ${resolvedPath}
211
+ Error: ${errorMessage}`);
220
212
  }
221
213
  }
222
214
  if (!configModule.default) {
@@ -345,7 +337,7 @@ var TypeGenerator = class {
345
337
  return `${this.prefix}Hit${this.postfix}`;
346
338
  }
347
339
  const lastPart = path[path.length - 1];
348
- const parts = lastPart.replace(/[\[\]]/g, "").split(/[-_\s]+/).filter(Boolean);
340
+ const parts = lastPart.replace(/[[\]]/g, "").split(/[-_\s]+/).filter(Boolean);
349
341
  const pascalCase = parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
350
342
  if (path.length > 1) {
351
343
  const parent = path[path.length - 2];
@@ -511,10 +503,7 @@ Set overwrite: true in config to allow overwriting existing files.`
511
503
  console.log(`App ID: ${generatorConfig.appId}`);
512
504
  let client;
513
505
  try {
514
- client = (0, import_algoliasearch.algoliasearch)(
515
- generatorConfig.appId,
516
- generatorConfig.searchKey
517
- );
506
+ client = (0, import_algoliasearch.algoliasearch)(generatorConfig.appId, generatorConfig.searchKey);
518
507
  } catch (error) {
519
508
  throw new Error(
520
509
  `Failed to initialize Algolia client: ${error instanceof Error ? error.message : String(error)}`
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  validateConfig,
4
4
  validateGeneratorConfig,
5
5
  validateUrlSchema
6
- } from "./chunk-R66ECGLW.js";
6
+ } from "./chunk-4T5DZK75.js";
7
7
  export {
8
8
  main,
9
9
  validateConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "algolia-codegen",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Generate TypeScript types from Algolia indices",
5
5
  "license": "MIT",
6
6
  "author": "nightlightmare",
@@ -45,11 +45,17 @@
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/node": "^25.0.3",
48
+ "@vitest/coverage-v8": "^2.1.8",
49
+ "eslint": "^9.39.2",
50
+ "eslint-config-prettier": "^10.1.8",
51
+ "prettier": "^3.7.4",
48
52
  "tsup": "^8.0.0",
49
- "typescript": "^5.4.0"
53
+ "typescript": "^5.4.0",
54
+ "typescript-eslint": "^8.50.1",
55
+ "vitest": "^2.1.8"
50
56
  },
51
57
  "engines": {
52
- "node": ">=18"
58
+ "node": ">=20"
53
59
  },
54
60
  "publishConfig": {
55
61
  "access": "public"
@@ -58,6 +64,13 @@
58
64
  "build": "tsup",
59
65
  "dev": "tsup --watch",
60
66
  "clean": "rm -rf dist",
61
- "type-check": "tsc --noEmit"
67
+ "type-check": "tsc --noEmit",
68
+ "lint": "eslint .",
69
+ "lint:fix": "eslint . --fix",
70
+ "format": "prettier --write .",
71
+ "format:check": "prettier --check .",
72
+ "test": "vitest run",
73
+ "test:watch": "vitest",
74
+ "test:coverage": "vitest run --coverage"
62
75
  }
63
76
  }