weflayr 0.22.0 → 0.22.1
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/.prettierignore +2 -0
- package/dist/api/caseConversion.d.ts +16 -0
- package/dist/api/caseConversion.js +45 -0
- package/dist/api/generated/client/client.gen.d.ts +2 -0
- package/dist/api/generated/client/client.gen.js +220 -0
- package/dist/api/generated/client/index.d.ts +10 -0
- package/dist/api/generated/client/index.js +17 -0
- package/dist/api/generated/client/types.gen.d.ts +120 -0
- package/dist/api/generated/client/types.gen.js +3 -0
- package/dist/api/generated/client/utils.gen.d.ts +37 -0
- package/dist/api/generated/client/utils.gen.js +239 -0
- package/dist/api/generated/client.gen.d.ts +12 -0
- package/dist/api/generated/client.gen.js +6 -0
- package/dist/api/generated/core/auth.gen.d.ts +25 -0
- package/dist/api/generated/core/auth.gen.js +18 -0
- package/dist/api/generated/core/bodySerializer.gen.d.ts +25 -0
- package/dist/api/generated/core/bodySerializer.gen.js +60 -0
- package/dist/api/generated/core/params.gen.d.ts +43 -0
- package/dist/api/generated/core/params.gen.js +112 -0
- package/dist/api/generated/core/pathSerializer.gen.d.ts +33 -0
- package/dist/api/generated/core/pathSerializer.gen.js +115 -0
- package/dist/api/generated/core/queryKeySerializer.gen.d.ts +18 -0
- package/dist/api/generated/core/queryKeySerializer.gen.js +98 -0
- package/dist/api/generated/core/serverSentEvents.gen.d.ts +71 -0
- package/dist/api/generated/core/serverSentEvents.gen.js +135 -0
- package/dist/api/generated/core/types.gen.d.ts +83 -0
- package/dist/api/generated/core/types.gen.js +3 -0
- package/dist/api/generated/core/utils.gen.d.ts +19 -0
- package/dist/api/generated/core/utils.gen.js +93 -0
- package/dist/api/generated/facade.gen.d.ts +208 -0
- package/dist/api/generated/facade.gen.js +52 -0
- package/dist/api/generated/index.d.ts +2 -0
- package/dist/api/generated/index.js +17 -0
- package/dist/api/generated/sdk.gen.d.ts +63 -0
- package/dist/api/generated/sdk.gen.js +125 -0
- package/dist/api/generated/types.gen.d.ts +711 -0
- package/dist/api/generated/types.gen.js +3 -0
- package/dist/api/index.d.ts +27 -0
- package/dist/api/index.js +48 -0
- package/dist/api/makeEndpoint.d.ts +27 -0
- package/dist/api/makeEndpoint.js +37 -0
- package/index.d.ts +24 -15
- package/openapi-ts.config.mjs +9 -0
- package/package.json +9 -2
- package/scripts/generate_facade.mjs +86 -0
- package/src/api/caseConversion.ts +58 -0
- package/src/api/generated/client/client.gen.ts +277 -0
- package/src/api/generated/client/index.ts +27 -0
- package/src/api/generated/client/types.gen.ts +218 -0
- package/src/api/generated/client/utils.gen.ts +316 -0
- package/src/api/generated/client.gen.ts +16 -0
- package/src/api/generated/core/auth.gen.ts +48 -0
- package/src/api/generated/core/bodySerializer.gen.ts +82 -0
- package/src/api/generated/core/params.gen.ts +178 -0
- package/src/api/generated/core/pathSerializer.gen.ts +171 -0
- package/src/api/generated/core/queryKeySerializer.gen.ts +117 -0
- package/src/api/generated/core/serverSentEvents.gen.ts +242 -0
- package/src/api/generated/core/types.gen.ts +110 -0
- package/src/api/generated/core/utils.gen.ts +140 -0
- package/src/api/generated/facade.gen.ts +80 -0
- package/src/api/generated/index.ts +4 -0
- package/src/api/generated/sdk.gen.ts +139 -0
- package/src/api/generated/types.gen.ts +792 -0
- package/src/api/index.ts +45 -0
- package/src/api/makeEndpoint.ts +54 -0
- package/src/auto-instrument.js +19 -6
- package/src/index.js +5 -0
- package/src/instrumentation/google/get_provider_name.js +2 -1
- package/src/instrumentation/vercel/full_instrumentation.js +44 -0
- package/src/otel/propagation.js +21 -12
- package/src/otel/span-processor.js +11 -1
- package/src/otel/vercel-ai-sdk-span-filter.js +79 -0
- package/tests/api/index.test.js +330 -0
- package/tests/index.test.js +48 -15
- package/tests/instrumentation/vercel/full_instrumentation.test.js +16 -0
- package/tests/otel/vercel-ai-sdk-span-filter.test.js +159 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entry point for the Weflayr public API (revenue, key metrics, customer tags).
|
|
3
|
+
*
|
|
4
|
+
* The endpoint functions and types under ./generated are produced from the
|
|
5
|
+
* platform's OpenAPI spec (`make generate-sdk-api-clients`) and speak the
|
|
6
|
+
* wire's snake_case, same as the Python SDK. generated/facade.gen.ts (also
|
|
7
|
+
* produced by that command, via scripts/generate_facade.mjs) wraps every
|
|
8
|
+
* endpoint so JS callers use camelCase instead - see ./caseConversion and
|
|
9
|
+
* ./makeEndpoint. This module is the handwritten front door that configures
|
|
10
|
+
* their client:
|
|
11
|
+
*
|
|
12
|
+
* const weflayr = require('weflayr');
|
|
13
|
+
*
|
|
14
|
+
* const apiClient = weflayr.client({ apiKey: 'wf-...' });
|
|
15
|
+
* const { data } = await weflayr.api.getAllRevenue({ client: apiClient, throwOnError: true });
|
|
16
|
+
*/
|
|
17
|
+
import { type Client } from './generated/client';
|
|
18
|
+
export declare const DEFAULT_BASE_URL = "https://app.weflayr.com/api";
|
|
19
|
+
export interface ApiClientOptions {
|
|
20
|
+
/** A project API key, created in the Weflayr dashboard. Defaults to the `WEFLAYR_API_KEY` environment variable. */
|
|
21
|
+
apiKey?: string;
|
|
22
|
+
/** Where the API lives — override for local or self-hosted deployments. */
|
|
23
|
+
baseUrl?: string;
|
|
24
|
+
}
|
|
25
|
+
/** A client for the public API, authenticated with a project API key. */
|
|
26
|
+
export declare function client(options?: ApiClientOptions): Client;
|
|
27
|
+
export * from './generated/facade.gen';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Entry point for the Weflayr public API (revenue, key metrics, customer tags).
|
|
4
|
+
*
|
|
5
|
+
* The endpoint functions and types under ./generated are produced from the
|
|
6
|
+
* platform's OpenAPI spec (`make generate-sdk-api-clients`) and speak the
|
|
7
|
+
* wire's snake_case, same as the Python SDK. generated/facade.gen.ts (also
|
|
8
|
+
* produced by that command, via scripts/generate_facade.mjs) wraps every
|
|
9
|
+
* endpoint so JS callers use camelCase instead - see ./caseConversion and
|
|
10
|
+
* ./makeEndpoint. This module is the handwritten front door that configures
|
|
11
|
+
* their client:
|
|
12
|
+
*
|
|
13
|
+
* const weflayr = require('weflayr');
|
|
14
|
+
*
|
|
15
|
+
* const apiClient = weflayr.client({ apiKey: 'wf-...' });
|
|
16
|
+
* const { data } = await weflayr.api.getAllRevenue({ client: apiClient, throwOnError: true });
|
|
17
|
+
*/
|
|
18
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
21
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
22
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
23
|
+
}
|
|
24
|
+
Object.defineProperty(o, k2, desc);
|
|
25
|
+
}) : (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
o[k2] = m[k];
|
|
28
|
+
}));
|
|
29
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
30
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
31
|
+
};
|
|
32
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
+
exports.DEFAULT_BASE_URL = void 0;
|
|
34
|
+
exports.client = client;
|
|
35
|
+
const client_1 = require("./generated/client");
|
|
36
|
+
exports.DEFAULT_BASE_URL = 'https://app.weflayr.com/api';
|
|
37
|
+
/** A client for the public API, authenticated with a project API key. */
|
|
38
|
+
function client(options = {}) {
|
|
39
|
+
const apiKey = options.apiKey || process.env.WEFLAYR_API_KEY;
|
|
40
|
+
if (!apiKey) {
|
|
41
|
+
throw new Error('weflayr.client: `apiKey` is required (pass it explicitly or set WEFLAYR_API_KEY)');
|
|
42
|
+
}
|
|
43
|
+
return (0, client_1.createClient)((0, client_1.createConfig)({
|
|
44
|
+
baseUrl: options.baseUrl ?? exports.DEFAULT_BASE_URL,
|
|
45
|
+
auth: () => apiKey,
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
__exportStar(require("./generated/facade.gen"), exports);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps a generated public-API endpoint function so its `body`/`path`/`query`
|
|
3
|
+
* are converted from camelCase to the wire's snake_case on the way out, and
|
|
4
|
+
* its `data`/`error` are converted back to camelCase on the way in.
|
|
5
|
+
*
|
|
6
|
+
* Instantiated once per endpoint in generated/facade.gen.ts, produced by
|
|
7
|
+
* scripts/generate_facade.mjs - see that script for how the per-endpoint
|
|
8
|
+
* type arguments are derived.
|
|
9
|
+
*/
|
|
10
|
+
import type { Client } from './generated/client';
|
|
11
|
+
export interface ClientOption {
|
|
12
|
+
client: Client;
|
|
13
|
+
throwOnError?: boolean;
|
|
14
|
+
headers?: HeadersInit;
|
|
15
|
+
}
|
|
16
|
+
export interface CallResult<TData, TError> {
|
|
17
|
+
data?: TData;
|
|
18
|
+
error?: TError;
|
|
19
|
+
request?: Request;
|
|
20
|
+
response?: Response;
|
|
21
|
+
}
|
|
22
|
+
type WireResult = {
|
|
23
|
+
data?: unknown;
|
|
24
|
+
error?: unknown;
|
|
25
|
+
};
|
|
26
|
+
export declare function makeEndpoint<TParams extends object, TData, TError>(wireFn: (options: any) => Promise<WireResult>): (options: ClientOption & TParams) => Promise<CallResult<TData, TError>>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Wraps a generated public-API endpoint function so its `body`/`path`/`query`
|
|
4
|
+
* are converted from camelCase to the wire's snake_case on the way out, and
|
|
5
|
+
* its `data`/`error` are converted back to camelCase on the way in.
|
|
6
|
+
*
|
|
7
|
+
* Instantiated once per endpoint in generated/facade.gen.ts, produced by
|
|
8
|
+
* scripts/generate_facade.mjs - see that script for how the per-endpoint
|
|
9
|
+
* type arguments are derived.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.makeEndpoint = makeEndpoint;
|
|
13
|
+
const caseConversion_1 = require("./caseConversion");
|
|
14
|
+
function makeEndpoint(
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
wireFn) {
|
|
17
|
+
return async (options) => {
|
|
18
|
+
const wireOptions = { ...options };
|
|
19
|
+
for (const key of ['body', 'path', 'query']) {
|
|
20
|
+
if (wireOptions[key] !== undefined) {
|
|
21
|
+
wireOptions[key] = (0, caseConversion_1.toSnakeCaseDeep)(wireOptions[key]);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
let result;
|
|
25
|
+
try {
|
|
26
|
+
result = await wireFn(wireOptions);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
throw (0, caseConversion_1.toCamelCaseDeep)(error);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
...result,
|
|
33
|
+
data: result.data !== undefined ? (0, caseConversion_1.toCamelCaseDeep)(result.data) : undefined,
|
|
34
|
+
error: result.error !== undefined ? (0, caseConversion_1.toCamelCaseDeep)(result.error) : undefined,
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -33,31 +33,29 @@ export const AiSdk: {
|
|
|
33
33
|
readonly OPENAI: 'openai';
|
|
34
34
|
readonly ANTHROPIC: 'anthropic';
|
|
35
35
|
readonly BEDROCK: 'bedrock';
|
|
36
|
-
readonly AZURE: 'azure';
|
|
37
36
|
readonly COHERE: 'cohere';
|
|
38
|
-
readonly TOGETHER: 'together';
|
|
39
|
-
readonly GOOGLE_VERTEX: 'google-vertex';
|
|
40
37
|
readonly GOOGLE_GENAI: 'google-genai';
|
|
41
38
|
readonly ELEVENLABS: 'elevenlabs';
|
|
39
|
+
readonly VERCEL_AI_GATEWAY: 'vercel_ai_gateway';
|
|
42
40
|
};
|
|
43
41
|
export type AiSdk = (typeof AiSdk)[keyof typeof AiSdk];
|
|
44
42
|
|
|
45
43
|
/**
|
|
46
|
-
* Allowed values for `propagateMetadata({ providerName })`:
|
|
47
|
-
*
|
|
48
|
-
*
|
|
44
|
+
* Allowed values for `propagateMetadata({ providerName })`: the actual AI
|
|
45
|
+
* providers a call can be attributed to. Unlike AiSdk this names providers,
|
|
46
|
+
* not SDKs — google-genai is an SDK whose calls are labelled gemini or vertex,
|
|
47
|
+
* and mistral is reached via the OpenAI SDK on the wire but reported as itself.
|
|
49
48
|
*/
|
|
50
49
|
export const AIProviderName: {
|
|
51
50
|
readonly OPENAI: 'openai';
|
|
52
51
|
readonly ANTHROPIC: 'anthropic';
|
|
53
52
|
readonly BEDROCK: 'bedrock';
|
|
54
|
-
readonly AZURE: 'azure';
|
|
55
53
|
readonly COHERE: 'cohere';
|
|
56
|
-
readonly TOGETHER: 'together';
|
|
57
|
-
readonly GOOGLE_VERTEX: 'google-vertex';
|
|
58
|
-
readonly GOOGLE_GENAI: 'google-genai';
|
|
59
54
|
readonly ELEVENLABS: 'elevenlabs';
|
|
55
|
+
readonly MISTRAL: 'mistral';
|
|
60
56
|
readonly VERCEL_AI_GATEWAY: 'vercel_ai_gateway';
|
|
57
|
+
readonly GEMINI: 'gemini';
|
|
58
|
+
readonly VERTEX: 'vertex';
|
|
61
59
|
};
|
|
62
60
|
export type AIProviderName = (typeof AIProviderName)[keyof typeof AIProviderName];
|
|
63
61
|
|
|
@@ -104,8 +102,10 @@ export function autoInstrument(
|
|
|
104
102
|
export interface PropagateMetadataOptions {
|
|
105
103
|
/** Product-side feature triggering the LLM call (e.g. `"onboarding-summary"`). */
|
|
106
104
|
featureName: string;
|
|
107
|
-
/**
|
|
108
|
-
|
|
105
|
+
/** Name of the end-user / tenant customer the call is made for. */
|
|
106
|
+
customerName?: string;
|
|
107
|
+
/** @deprecated Use `customerName` instead. */
|
|
108
|
+
customerId?: string;
|
|
109
109
|
/**
|
|
110
110
|
* Override for the provider seen on the wire. Set this only if it differs
|
|
111
111
|
* from the SDK auto-detected by OpenTelemetry — e.g. calls routed through
|
|
@@ -116,7 +116,7 @@ export interface PropagateMetadataOptions {
|
|
|
116
116
|
/**
|
|
117
117
|
* Arbitrary scalar key/value pairs to attach. Values must be `string`,
|
|
118
118
|
* `number`, or `boolean` so they survive the ClickHouse `Map(String,
|
|
119
|
-
* String)` column they land in. Avoid `featureName` / `
|
|
119
|
+
* String)` column they land in. Avoid `featureName` / `customerName` keys
|
|
120
120
|
* here — they have dedicated fields.
|
|
121
121
|
*/
|
|
122
122
|
extraTags?: Record<string, string | number | boolean>;
|
|
@@ -131,7 +131,7 @@ export interface PropagateMetadataOptions {
|
|
|
131
131
|
*
|
|
132
132
|
* @example
|
|
133
133
|
* await weflayr.propagateMetadata(
|
|
134
|
-
* { featureName: 'onboarding',
|
|
134
|
+
* { featureName: 'onboarding', customerName: 'Acme Corp', extraTags: { variant: 'control' } },
|
|
135
135
|
* async () => { await openai.chat.completions.create({ ... }); },
|
|
136
136
|
* );
|
|
137
137
|
*/
|
|
@@ -146,7 +146,7 @@ export function propagateMetadata<T>(
|
|
|
146
146
|
*
|
|
147
147
|
* @example
|
|
148
148
|
* class ChatService {
|
|
149
|
-
* @weflayr.propagateMetadata({ featureName: 'chat',
|
|
149
|
+
* @weflayr.propagateMetadata({ featureName: 'chat', customerName: 'Acme Corp' })
|
|
150
150
|
* async reply(userId: string) { ... }
|
|
151
151
|
* }
|
|
152
152
|
*/
|
|
@@ -156,3 +156,12 @@ export function propagateMetadata(
|
|
|
156
156
|
|
|
157
157
|
/** Flush any pending spans. Await before a short-lived runtime exits. */
|
|
158
158
|
export function flush(): Promise<void>;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Endpoint functions for the Weflayr public API (revenue, key metrics, customer
|
|
162
|
+
* tags), generated from the platform's OpenAPI spec.
|
|
163
|
+
*/
|
|
164
|
+
export * as api from './dist/api';
|
|
165
|
+
|
|
166
|
+
/** A client for the public API, authenticated with a project API key. */
|
|
167
|
+
export { client, type ApiClientOptions } from './dist/api';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { defineConfig } from '@hey-api/openapi-ts';
|
|
2
|
+
|
|
3
|
+
// Regenerated by `make generate-sdk-api-clients`; src/api/generated/ is wiped
|
|
4
|
+
// on every run, the handwritten facade lives in src/api/index.ts.
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
input: '../observability_dashboard/app_api/openapi.json',
|
|
7
|
+
output: 'src/api/generated',
|
|
8
|
+
plugins: ['@hey-api/client-fetch'],
|
|
9
|
+
});
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "weflayr",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.1",
|
|
4
4
|
"description": "Weflayr Node.js SDK — instrument any LLM client via JS Proxy",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"generate-api": "openapi-ts && node scripts/generate_facade.mjs",
|
|
10
|
+
"pretest": "npm run build",
|
|
11
|
+
"prepublishOnly": "npm run build",
|
|
8
12
|
"test": "node --test",
|
|
9
13
|
"lint": "eslint src tests",
|
|
10
14
|
"lint:fix": "eslint src tests --fix",
|
|
@@ -31,7 +35,10 @@
|
|
|
31
35
|
"async-retry": "^1.3.3"
|
|
32
36
|
},
|
|
33
37
|
"devDependencies": {
|
|
38
|
+
"@hey-api/openapi-ts": "^0.99.0",
|
|
39
|
+
"@types/node": "^26.1.1",
|
|
34
40
|
"eslint": "^9.18.0",
|
|
35
|
-
"prettier": "^3.4.2"
|
|
41
|
+
"prettier": "^3.4.2",
|
|
42
|
+
"typescript": "^6.0.3"
|
|
36
43
|
}
|
|
37
44
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Generates src/api/generated/facade.gen.ts: one camelCase-facing wrapper
|
|
5
|
+
// per public-API endpoint exported from src/api/generated/sdk.gen.ts.
|
|
6
|
+
//
|
|
7
|
+
// Run via `npm run generate-api`, right after `openapi-ts` regenerates
|
|
8
|
+
// src/api/generated/ (which wipes the directory first, so this always
|
|
9
|
+
// starts from a clean slate). Do not edit facade.gen.ts by hand - it is
|
|
10
|
+
// overwritten on every run.
|
|
11
|
+
//
|
|
12
|
+
// @hey-api/openapi-ts names an operation's request/response types after its
|
|
13
|
+
// PascalCase operation name (e.g. operation `getAllRevenue` gets types
|
|
14
|
+
// `GetAllRevenueData`, `GetAllRevenueResponse`, `GetAllRevenueError`), so the
|
|
15
|
+
// only thing this script needs from the generated source is the list of
|
|
16
|
+
// operation names - the type names, and the per-endpoint camelCase/snake_case
|
|
17
|
+
// conversion, follow mechanically from there.
|
|
18
|
+
|
|
19
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
|
|
23
|
+
const GENERATED_DIR = path.join(
|
|
24
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
25
|
+
'..',
|
|
26
|
+
'src',
|
|
27
|
+
'api',
|
|
28
|
+
'generated'
|
|
29
|
+
);
|
|
30
|
+
const SDK_PATH = path.join(GENERATED_DIR, 'sdk.gen.ts');
|
|
31
|
+
const TYPES_PATH = path.join(GENERATED_DIR, 'types.gen.ts');
|
|
32
|
+
const OUTPUT_PATH = path.join(GENERATED_DIR, 'facade.gen.ts');
|
|
33
|
+
|
|
34
|
+
const HEADER = `// Generated by services/sdk_node_js/scripts/generate_facade.mjs from
|
|
35
|
+
// sdk.gen.ts. Do not edit by hand.
|
|
36
|
+
|
|
37
|
+
import { makeEndpoint } from '../makeEndpoint.js';
|
|
38
|
+
import type { CamelCaseDeep } from '../caseConversion.js';
|
|
39
|
+
import * as generated from './sdk.gen.js';
|
|
40
|
+
import type * as generatedTypes from './types.gen.js';
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
function pascalCase(operationName) {
|
|
44
|
+
return operationName[0].toUpperCase() + operationName.slice(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function collectOperationNames(sdkSource) {
|
|
48
|
+
return [...sdkSource.matchAll(/^export const (\w+) = </gm)].map((match) => match[1]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function collectExportedTypeNames(typesSource) {
|
|
52
|
+
return new Set([...typesSource.matchAll(/^export type (\w+)/gm)].map((match) => match[1]));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function main() {
|
|
56
|
+
const sdkSource = readFileSync(SDK_PATH, 'utf8');
|
|
57
|
+
const typesSource = readFileSync(TYPES_PATH, 'utf8');
|
|
58
|
+
const operationNames = collectOperationNames(sdkSource);
|
|
59
|
+
const exportedTypeNames = collectExportedTypeNames(typesSource);
|
|
60
|
+
|
|
61
|
+
const lines = [HEADER];
|
|
62
|
+
for (const operationName of operationNames) {
|
|
63
|
+
const pascal = pascalCase(operationName);
|
|
64
|
+
for (const suffix of ['Data', 'Response', 'Error']) {
|
|
65
|
+
const typeName = `${pascal}${suffix}`;
|
|
66
|
+
if (!exportedTypeNames.has(typeName)) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`generate_facade.mjs: expected type '${typeName}' for operation '${operationName}' ` +
|
|
69
|
+
"not found in types.gen.ts - has @hey-api/openapi-ts's naming convention changed?"
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
lines.push(
|
|
74
|
+
`export const ${operationName} = makeEndpoint<\n` +
|
|
75
|
+
` CamelCaseDeep<Omit<generatedTypes.${pascal}Data, 'url'>>,\n` +
|
|
76
|
+
` CamelCaseDeep<generatedTypes.${pascal}Response>,\n` +
|
|
77
|
+
` CamelCaseDeep<generatedTypes.${pascal}Error>\n` +
|
|
78
|
+
`>(generated.${operationName});`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
writeFileSync(OUTPUT_PATH, lines.join('\n\n') + '\n');
|
|
83
|
+
console.log(`Generated ${OUTPUT_PATH} (${operationNames.length} endpoints)`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
main();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts between the wire's snake_case JSON keys (also the Python SDK's
|
|
3
|
+
* native casing) and the camelCase keys this SDK exposes to JS callers.
|
|
4
|
+
*
|
|
5
|
+
* `tags` (see CustomerTags / CustomerTagsOutput) is a free-form map of
|
|
6
|
+
* user-chosen tag names, not a fixed schema - its own field name is renamed
|
|
7
|
+
* like any other, but the keys inside it are left untouched in both
|
|
8
|
+
* directions, otherwise a tag literally named e.g. "utm_source" would be
|
|
9
|
+
* silently mangled into "utmSource" on the way out.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const OPAQUE_KEYS = new Set(['tags']);
|
|
13
|
+
|
|
14
|
+
export type CamelCase<S extends string> = S extends `${infer Head}_${infer Tail}`
|
|
15
|
+
? `${Head}${Capitalize<CamelCase<Tail>>}`
|
|
16
|
+
: S;
|
|
17
|
+
|
|
18
|
+
export type CamelCaseDeep<T> = T extends readonly (infer Item)[]
|
|
19
|
+
? CamelCaseDeep<Item>[]
|
|
20
|
+
: T extends object
|
|
21
|
+
? { [Key in keyof T as CamelCase<Key & string>]: CamelCaseDeep<T[Key]> }
|
|
22
|
+
: T;
|
|
23
|
+
|
|
24
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
25
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function snakeToCamel(key: string): string {
|
|
29
|
+
return key.replace(/_[a-z0-9]/g, (match) => match[1].toUpperCase());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function camelToSnake(key: string): string {
|
|
33
|
+
return key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function convertKeysDeep(value: unknown, convertKey: (key: string) => string): unknown {
|
|
37
|
+
if (Array.isArray(value)) {
|
|
38
|
+
return value.map((item) => convertKeysDeep(item, convertKey));
|
|
39
|
+
}
|
|
40
|
+
if (!isPlainObject(value)) {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
const converted: Record<string, unknown> = {};
|
|
44
|
+
for (const [key, entryValue] of Object.entries(value)) {
|
|
45
|
+
converted[convertKey(key)] = OPAQUE_KEYS.has(key)
|
|
46
|
+
? entryValue
|
|
47
|
+
: convertKeysDeep(entryValue, convertKey);
|
|
48
|
+
}
|
|
49
|
+
return converted;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function toCamelCaseDeep<T>(value: T): CamelCaseDeep<T> {
|
|
53
|
+
return convertKeysDeep(value, snakeToCamel) as CamelCaseDeep<T>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function toSnakeCaseDeep(value: unknown): unknown {
|
|
57
|
+
return convertKeysDeep(value, camelToSnake);
|
|
58
|
+
}
|