@voyantjs/cloud-sdk 0.4.0 → 0.6.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/README.md CHANGED
@@ -14,6 +14,8 @@ Public TypeScript client for Voyant Cloud APIs.
14
14
  long-running crawls and keep-alive Puppeteer sessions)
15
15
  - video (manage uploads, playback, captions, watermarks, signed playback
16
16
  tokens; create videos from a public URL)
17
+ - search (`createSearchClientConfig` returns a Typesense client config pointed
18
+ at the Voyant search proxy)
17
19
 
18
20
  ## Install
19
21
 
@@ -34,6 +36,49 @@ const vaults = await client.vault.listVaults();
34
36
  const phoneNumbers = await client.sms.listPhoneNumbers();
35
37
  ```
36
38
 
39
+ ### From env / worker bindings
40
+
41
+ `getVoyantCloudClient` reads `VOYANT_CLOUD_API_KEY` (and optionally
42
+ `VOYANT_CLOUD_API_URL`, `VOYANT_CLOUD_USER_AGENT`) from a bindings/env
43
+ object and constructs a client. It throws a typed `VoyantCloudConfigError`
44
+ when the key is missing.
45
+
46
+ ```ts
47
+ import { getVoyantCloudClient, type VoyantCloudEnv } from "@voyantjs/cloud-sdk";
48
+
49
+ // Cloudflare Worker
50
+ export default {
51
+ async fetch(_req: Request, env: VoyantCloudEnv) {
52
+ const cloud = getVoyantCloudClient(env);
53
+ return Response.json(await cloud.vault.listVaults());
54
+ },
55
+ };
56
+ ```
57
+
58
+ ```ts
59
+ import { getVoyantCloudClient } from "@voyantjs/cloud-sdk";
60
+
61
+ // Node
62
+ const cloud = getVoyantCloudClient(process.env);
63
+ const vaults = await cloud.vault.listVaults();
64
+ ```
65
+
66
+ `overrides` win over env values, except an empty-string override is
67
+ treated as missing so it can't silently clobber a valid env value:
68
+
69
+ ```ts
70
+ import { getVoyantCloudClient, type VoyantCloudEnv } from "@voyantjs/cloud-sdk";
71
+
72
+ declare const env: VoyantCloudEnv;
73
+ declare const tenantKey: string;
74
+
75
+ const cloud = getVoyantCloudClient(env, { apiKey: tenantKey });
76
+ ```
77
+
78
+ For paths that legitimately operate without cloud (local dev tooling that
79
+ doesn't send mail, etc.), use `tryGetVoyantCloudClient` — it returns
80
+ `null` instead of throwing when the key is unset.
81
+
37
82
  ## Shape
38
83
 
39
84
  Root groups:
@@ -44,6 +89,7 @@ Root groups:
44
89
  - `email`
45
90
  - `browser`
46
91
  - `video`
92
+ - `search` (standalone `createSearchClientConfig` export, not on the client)
47
93
 
48
94
  The `vault` group covers list-vaults, list-secrets, and get-secret routes.
49
95
 
@@ -83,6 +129,49 @@ await client.browser.sessions.runCommands(session.id, {
83
129
  await client.browser.sessions.close(session.id);
84
130
  ```
85
131
 
132
+ The `search` surface is a config helper, not a wrapped client. Voyant's
133
+ search proxy speaks the Typesense HTTP protocol, so `createSearchClientConfig`
134
+ hands back a config you pass straight to the official `typesense` package:
135
+
136
+ ```ts
137
+ import { Client } from "typesense";
138
+ import { createSearchClientConfig } from "@voyantjs/cloud-sdk";
139
+
140
+ const search = new Client(
141
+ createSearchClientConfig({
142
+ apiKey: process.env.VOYANT_API_KEY!,
143
+ organizationSlug: "acme",
144
+ projectName: "catalog",
145
+ }),
146
+ );
147
+
148
+ await search.collections().create({
149
+ name: "products",
150
+ fields: [
151
+ { name: "name", type: "string" },
152
+ { name: "tags", type: "string[]", facet: true },
153
+ ],
154
+ });
155
+
156
+ await search
157
+ .collections("products")
158
+ .documents()
159
+ .import([{ id: "1", name: "Sneakers", tags: ["shoes"] }], {
160
+ action: "upsert",
161
+ });
162
+
163
+ const hits = await search
164
+ .collections("products")
165
+ .documents()
166
+ .search({ q: "sneak", query_by: "name,tags" });
167
+ ```
168
+
169
+ `apiKey` is your Voyant API token (`search:read` for queries,
170
+ `search:write` for writes). The proxy auths with `Authorization: Bearer ...`,
171
+ substitutes the project's scoped Typesense key downstream, and rewrites
172
+ collection names so isolation prefixes never leak into your code. `typesense`
173
+ is a peer requirement — install it alongside the SDK if you use search.
174
+
86
175
  The `video` group covers the Voyant video service: `videos.{list, get,
87
176
  createUpload, createFromUrl, update, delete, enableDownload, mintToken}`,
88
177
  captions under `videos.captions.{list, upload, generate, delete}`, and
@@ -127,6 +216,7 @@ Useful exported types include:
127
216
  `GenerateVideoCaptionInput`, `CreateVideoWatermarkInput`,
128
217
  `VideoStatus`, `VideoCaptionStatus`, `VideoDownloadStatus`,
129
218
  `VideoWatermarkPosition`
219
+ - `SearchClientConfig`, `SearchClientConfigOptions`
130
220
  - `PhoneNumberStatus`, `SmsMessageStatus`, `VerificationChannel`,
131
221
  `VerificationAttemptStatus`, `EmailMessageStatus`,
132
222
  `BrowserSessionStatus`, `BrowserJobStatus`
@@ -141,7 +231,7 @@ Useful exported types include:
141
231
  `verification:read`, `emails:read`, `emails:send`, `browser:render`,
142
232
  `browser:scrape`, `browser:extract`, `browser:crawl`, `browser:sessions`,
143
233
  `video:read`, `video:upload`, `video:delete`, `video:captions:write`,
144
- `video:watermarks:write`);
234
+ `video:watermarks:write`, `search:read`, `search:write`);
145
235
  requests fail with `403` if the token does not include the required scope
146
236
 
147
237
  For repo-level context, see [../../docs/cloud.md](../../docs/cloud.md).
package/dist/env.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ import { VoyantCloudClient } from "./client.js";
2
+ import type { VoyantCloudClientOptions } from "./types.js";
3
+ /**
4
+ * Bindings/env shape recognized by {@link getVoyantCloudClient} and
5
+ * {@link tryGetVoyantCloudClient}.
6
+ *
7
+ * Designed to accept a Cloudflare Worker `env` object, Node `process.env`,
8
+ * or any other key/value bag of strings. Empty-string values are treated
9
+ * the same as `undefined` — common when `.env` files leave a key blank.
10
+ */
11
+ export interface VoyantCloudEnv {
12
+ VOYANT_CLOUD_API_KEY?: string;
13
+ VOYANT_CLOUD_API_URL?: string;
14
+ VOYANT_CLOUD_USER_AGENT?: string;
15
+ }
16
+ /**
17
+ * Thrown when {@link getVoyantCloudClient} cannot construct a client because
18
+ * `VOYANT_CLOUD_API_KEY` is missing and no override was supplied.
19
+ */
20
+ export declare class VoyantCloudConfigError extends Error {
21
+ constructor(message: string);
22
+ }
23
+ /**
24
+ * Construct a {@link VoyantCloudClient} from a runtime bindings object
25
+ * (Cloudflare Worker `env`, Node `process.env`, etc.).
26
+ *
27
+ * `overrides` take precedence over env values, except an empty-string
28
+ * override is treated as missing so it can't silently clobber a valid env
29
+ * value.
30
+ *
31
+ * Throws {@link VoyantCloudConfigError} when no API key can be resolved
32
+ * from either source.
33
+ */
34
+ export declare function getVoyantCloudClient(env: VoyantCloudEnv, overrides?: Partial<VoyantCloudClientOptions>): VoyantCloudClient;
35
+ /**
36
+ * Like {@link getVoyantCloudClient}, but returns `null` when the API key
37
+ * is unset instead of throwing. Use from edges that legitimately operate
38
+ * without cloud (e.g. local dev tooling that doesn't send mail).
39
+ */
40
+ export declare function tryGetVoyantCloudClient(env: VoyantCloudEnv, overrides?: Partial<VoyantCloudClientOptions>): VoyantCloudClient | null;
41
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE3D;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,cAAc,EACnB,SAAS,GAAE,OAAO,CAAC,wBAAwB,CAAM,GAChD,iBAAiB,CAmBnB;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,cAAc,EACnB,SAAS,GAAE,OAAO,CAAC,wBAAwB,CAAM,GAChD,iBAAiB,GAAG,IAAI,CAS1B"}
package/dist/env.js ADDED
@@ -0,0 +1,55 @@
1
+ import { createVoyantCloudClient } from "./client.js";
2
+ /**
3
+ * Thrown when {@link getVoyantCloudClient} cannot construct a client because
4
+ * `VOYANT_CLOUD_API_KEY` is missing and no override was supplied.
5
+ */
6
+ export class VoyantCloudConfigError extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = "VoyantCloudConfigError";
10
+ }
11
+ }
12
+ /**
13
+ * Construct a {@link VoyantCloudClient} from a runtime bindings object
14
+ * (Cloudflare Worker `env`, Node `process.env`, etc.).
15
+ *
16
+ * `overrides` take precedence over env values, except an empty-string
17
+ * override is treated as missing so it can't silently clobber a valid env
18
+ * value.
19
+ *
20
+ * Throws {@link VoyantCloudConfigError} when no API key can be resolved
21
+ * from either source.
22
+ */
23
+ export function getVoyantCloudClient(env, overrides = {}) {
24
+ const apiKey = nonEmpty(overrides.apiKey) ?? nonEmpty(env.VOYANT_CLOUD_API_KEY);
25
+ if (!apiKey) {
26
+ throw new VoyantCloudConfigError("VOYANT_CLOUD_API_KEY is not set. Set the env variable or pass `apiKey` in overrides.");
27
+ }
28
+ const baseUrl = nonEmpty(env.VOYANT_CLOUD_API_URL);
29
+ const userAgent = nonEmpty(env.VOYANT_CLOUD_USER_AGENT);
30
+ return createVoyantCloudClient({
31
+ ...(baseUrl ? { baseUrl } : {}),
32
+ ...(userAgent ? { userAgent } : {}),
33
+ ...overrides,
34
+ apiKey,
35
+ });
36
+ }
37
+ /**
38
+ * Like {@link getVoyantCloudClient}, but returns `null` when the API key
39
+ * is unset instead of throwing. Use from edges that legitimately operate
40
+ * without cloud (e.g. local dev tooling that doesn't send mail).
41
+ */
42
+ export function tryGetVoyantCloudClient(env, overrides = {}) {
43
+ try {
44
+ return getVoyantCloudClient(env, overrides);
45
+ }
46
+ catch (error) {
47
+ if (error instanceof VoyantCloudConfigError) {
48
+ return null;
49
+ }
50
+ throw error;
51
+ }
52
+ }
53
+ function nonEmpty(value) {
54
+ return typeof value === "string" && value.length > 0 ? value : undefined;
55
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,7 @@
1
1
  export { createVoyantCloudClient, VoyantCloudClient } from "./client.js";
2
+ export { getVoyantCloudClient, tryGetVoyantCloudClient, VoyantCloudConfigError, } from "./env.js";
3
+ export type { VoyantCloudEnv } from "./env.js";
4
+ export { createSearchClientConfig } from "./search.js";
5
+ export type { SearchClientConfig, SearchClientConfigOptions, } from "./search.js";
2
6
  export type { BrowserCommand, BrowserCommandResult, BrowserCookie, BrowserCrawlSummary, BrowserGoToOptions, BrowserJobKind, BrowserJobStatus, BrowserJsonInput, BrowserLink, BrowserPdfInput, BrowserPdfOptions, BrowserRenderInput, BrowserSameSite, BrowserScrapeElement, BrowserScrapeInput, BrowserScrapeResult, BrowserScreenshotInput, BrowserScreenshotOptions, BrowserSessionStatus, BrowserSessionSummary, BrowserSnapshotResult, BrowserViewport, BrowserWaitForSelector, BrowserWaitUntil, CheckVerificationInput, CreateVideoFromUrlInput, CreateVideoUploadInput, CreateVideoWatermarkInput, EmailMessageStatus, EmailMessageSummary, GenerateVideoCaptionInput, MintVideoSignedTokenInput, OpenBrowserSessionInput, PhoneNumberCapabilities, PhoneNumberStatus, PhoneNumberSummary, RunBrowserCommandsInput, RunBrowserCommandsResult, SendEmailAttachment, SendEmailInput, SendSmsInput, SmsMessageStatus, SmsMessageSummary, StartBrowserCrawlInput, StartBrowserCrawlResult, StartVerificationInput, UpdateVideoInput, UploadVideoCaptionInput, VaultSecretSummary, VaultSecretValue, VaultSummary, VerificationAttemptStatus, VerificationAttemptSummary, VerificationChannel, VerificationCheckResult, VideoCaptionStatus, VideoCaptionSummary, VideoDownloadStatus, VideoSignedToken, VideoStatus, VideoSummary, VideoUploadTicket, VideoWatermarkPosition, VideoWatermarkProfileSummary, VoyantCloudClientOptions, } from "./types.js";
3
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACzE,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,eAAe,EACf,sBAAsB,EACtB,gBAAgB,EAChB,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,EACtB,yBAAyB,EACzB,kBAAkB,EAClB,mBAAmB,EACnB,yBAAyB,EACzB,yBAAyB,EACzB,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,EACjB,kBAAkB,EAClB,uBAAuB,EACvB,wBAAwB,EACxB,mBAAmB,EACnB,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,EACtB,gBAAgB,EAChB,uBAAuB,EACvB,kBAAkB,EAClB,gBAAgB,EAChB,YAAY,EACZ,yBAAyB,EACzB,0BAA0B,EAC1B,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,sBAAsB,EACtB,4BAA4B,EAC5B,wBAAwB,GACzB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AACvD,YAAY,EACV,kBAAkB,EAClB,yBAAyB,GAC1B,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,eAAe,EACf,sBAAsB,EACtB,gBAAgB,EAChB,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,EACtB,yBAAyB,EACzB,kBAAkB,EAClB,mBAAmB,EACnB,yBAAyB,EACzB,yBAAyB,EACzB,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,EACjB,kBAAkB,EAClB,uBAAuB,EACvB,wBAAwB,EACxB,mBAAmB,EACnB,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,EACtB,gBAAgB,EAChB,uBAAuB,EACvB,kBAAkB,EAClB,gBAAgB,EAChB,YAAY,EACZ,yBAAyB,EACzB,0BAA0B,EAC1B,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,sBAAsB,EACtB,4BAA4B,EAC5B,wBAAwB,GACzB,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1 +1,3 @@
1
1
  export { createVoyantCloudClient, VoyantCloudClient } from "./client.js";
2
+ export { getVoyantCloudClient, tryGetVoyantCloudClient, VoyantCloudConfigError, } from "./env.js";
3
+ export { createSearchClientConfig } from "./search.js";
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Configuration helper for the Voyant search surface.
3
+ *
4
+ * Voyant's search-api is a thin proxy in front of Typesense. It auths
5
+ * incoming requests against a Voyant API token, picks the right scoped
6
+ * Typesense key (read or write) server-side, and rewrites collection
7
+ * names so customers never see our internal isolation prefix.
8
+ *
9
+ * The wire protocol is pure Typesense, so we don't ship a hand-rolled
10
+ * SDK surface for it. Instead, this helper produces a config object
11
+ * that you pass directly to the official `typesense` client:
12
+ *
13
+ * ```ts
14
+ * import { Client } from "typesense";
15
+ * import { createSearchClientConfig } from "@voyantjs/cloud-sdk";
16
+ *
17
+ * const search = new Client(createSearchClientConfig({
18
+ * apiKey: process.env.VOYANT_API_TOKEN!,
19
+ * organizationSlug: "acme",
20
+ * projectName: "catalog",
21
+ * }));
22
+ *
23
+ * await search.collections().create({
24
+ * name: "products",
25
+ * fields: [{ name: "name", type: "string" }],
26
+ * });
27
+ * ```
28
+ *
29
+ * `apiKey` here is your Voyant API token — it is sent as
30
+ * `Authorization: Bearer ...`. The proxy strips Typesense's own
31
+ * `X-TYPESENSE-API-KEY` header from inbound requests and injects the
32
+ * project's scoped key downstream.
33
+ */
34
+ export interface SearchClientConfigOptions {
35
+ /** Voyant API token with `search:read` (queries) or `search:write` (writes) scope. */
36
+ apiKey: string;
37
+ /** Organization slug — the first path segment in the search URL. */
38
+ organizationSlug: string;
39
+ /** Search project name — the second path segment in the search URL. */
40
+ projectName: string;
41
+ /** Override the default `search.voyantjs.com` host (e.g. for local dev). */
42
+ host?: string;
43
+ /** Override the default port. */
44
+ port?: number;
45
+ /** Override the default `https` protocol. */
46
+ protocol?: string;
47
+ /** Additional headers to merge with `Authorization`. */
48
+ additionalHeaders?: Record<string, string>;
49
+ }
50
+ export interface SearchClientConfig {
51
+ apiKey: string;
52
+ nodes: Array<{
53
+ host: string;
54
+ port: number;
55
+ protocol: string;
56
+ path: string;
57
+ }>;
58
+ additionalHeaders: Record<string, string>;
59
+ }
60
+ /**
61
+ * Build a Typesense client configuration that targets the Voyant
62
+ * search proxy. Pass the returned object straight to
63
+ * `new Typesense.Client(...)`.
64
+ */
65
+ export declare function createSearchClientConfig(options: SearchClientConfigOptions): SearchClientConfig;
66
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAMH,MAAM,WAAW,yBAAyB;IACxC,sFAAsF;IACtF,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,gBAAgB,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,KAAK,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IACH,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3C;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,yBAAyB,GACjC,kBAAkB,CA0CpB"}
package/dist/search.js ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Configuration helper for the Voyant search surface.
3
+ *
4
+ * Voyant's search-api is a thin proxy in front of Typesense. It auths
5
+ * incoming requests against a Voyant API token, picks the right scoped
6
+ * Typesense key (read or write) server-side, and rewrites collection
7
+ * names so customers never see our internal isolation prefix.
8
+ *
9
+ * The wire protocol is pure Typesense, so we don't ship a hand-rolled
10
+ * SDK surface for it. Instead, this helper produces a config object
11
+ * that you pass directly to the official `typesense` client:
12
+ *
13
+ * ```ts
14
+ * import { Client } from "typesense";
15
+ * import { createSearchClientConfig } from "@voyantjs/cloud-sdk";
16
+ *
17
+ * const search = new Client(createSearchClientConfig({
18
+ * apiKey: process.env.VOYANT_API_TOKEN!,
19
+ * organizationSlug: "acme",
20
+ * projectName: "catalog",
21
+ * }));
22
+ *
23
+ * await search.collections().create({
24
+ * name: "products",
25
+ * fields: [{ name: "name", type: "string" }],
26
+ * });
27
+ * ```
28
+ *
29
+ * `apiKey` here is your Voyant API token — it is sent as
30
+ * `Authorization: Bearer ...`. The proxy strips Typesense's own
31
+ * `X-TYPESENSE-API-KEY` header from inbound requests and injects the
32
+ * project's scoped key downstream.
33
+ */
34
+ const DEFAULT_SEARCH_HOST = "search.voyantjs.com";
35
+ const DEFAULT_SEARCH_PORT = 443;
36
+ const DEFAULT_SEARCH_PROTOCOL = "https";
37
+ /**
38
+ * Build a Typesense client configuration that targets the Voyant
39
+ * search proxy. Pass the returned object straight to
40
+ * `new Typesense.Client(...)`.
41
+ */
42
+ export function createSearchClientConfig(options) {
43
+ const { apiKey, organizationSlug, projectName, host = DEFAULT_SEARCH_HOST, port = DEFAULT_SEARCH_PORT, protocol = DEFAULT_SEARCH_PROTOCOL, additionalHeaders, } = options;
44
+ if (!apiKey) {
45
+ throw new Error("createSearchClientConfig: `apiKey` is required.");
46
+ }
47
+ if (!organizationSlug) {
48
+ throw new Error("createSearchClientConfig: `organizationSlug` is required.");
49
+ }
50
+ if (!projectName) {
51
+ throw new Error("createSearchClientConfig: `projectName` is required.");
52
+ }
53
+ return {
54
+ // typesense-js requires `apiKey`. The proxy strips the
55
+ // X-TYPESENSE-API-KEY header it adds and uses our bearer token
56
+ // instead, so this value is ignored downstream — but it must be
57
+ // a non-empty string for the client to construct.
58
+ apiKey: "voyant-bearer",
59
+ nodes: [
60
+ {
61
+ host,
62
+ port,
63
+ protocol,
64
+ path: `/${organizationSlug}/${projectName}`,
65
+ },
66
+ ],
67
+ additionalHeaders: {
68
+ ...additionalHeaders,
69
+ Authorization: `Bearer ${apiKey}`,
70
+ },
71
+ };
72
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyantjs/cloud-sdk",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Public TypeScript SDK for Voyant Cloud APIs.",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "repository": {
@@ -37,9 +37,18 @@
37
37
  "dependencies": {
38
38
  "@voyant-sdk/sdk-core": "0.2.0"
39
39
  },
40
+ "peerDependencies": {
41
+ "typesense": "^2.0.0"
42
+ },
43
+ "peerDependenciesMeta": {
44
+ "typesense": {
45
+ "optional": true
46
+ }
47
+ },
40
48
  "devDependencies": {
41
49
  "eslint": "^9.39.1",
42
50
  "typescript": "5.9.2",
51
+ "typesense": "^2.0.0",
43
52
  "@voyant-sdk/eslint-config": "0.0.0",
44
53
  "@voyant-sdk/typescript-config": "0.0.0"
45
54
  },