@myapihq/sdk 2.4.0 → 2.4.2
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/dist/client.d.ts +23 -2
- package/dist/client.js +174 -22
- package/dist/crm.d.ts +2 -0
- package/dist/crm.js +15 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +11 -0
- package/package.json +11 -2
- package/scripts/scaffold-sdk.js +0 -501
- package/src/audience.ts +0 -162
- package/src/auth.ts +0 -161
- package/src/client.ts +0 -121
- package/src/company.ts +0 -48
- package/src/config.ts +0 -39
- package/src/container.ts +0 -187
- package/src/crm.ts +0 -239
- package/src/database.ts +0 -106
- package/src/domain.ts +0 -246
- package/src/email.ts +0 -247
- package/src/exposes.ts +0 -16
- package/src/function.ts +0 -141
- package/src/funds.ts +0 -66
- package/src/funnel.ts +0 -203
- package/src/git.ts +0 -171
- package/src/hq.ts +0 -393
- package/src/image.ts +0 -67
- package/src/index.ts +0 -32
- package/src/llm.ts +0 -189
- package/src/payments.ts +0 -98
- package/src/people.ts +0 -84
- package/src/pixel.ts +0 -80
- package/src/queue.ts +0 -115
- package/src/services.ts +0 -323
- package/src/storage.ts +0 -80
- package/src/task.ts +0 -198
- package/src/types.ts +0 -15
- package/src/url.ts +0 -18
- package/src/webhook.ts +0 -88
- package/src/workflow.ts +0 -162
- package/tsconfig.json +0 -14
package/scripts/scaffold-sdk.js
DELETED
|
@@ -1,501 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// SDK scaffolder. NOT a fully-autonomous codegen — it's a schema-driven
|
|
3
|
-
// boilerplate emitter where you (the human) choose function names, type
|
|
4
|
-
// aliases, and response shapes; the schema is the truth source for which
|
|
5
|
-
// endpoints exist, their methods, paths, path params, and request bodies.
|
|
6
|
-
//
|
|
7
|
-
// Usage:
|
|
8
|
-
// node scripts/scaffold-sdk.js ← scaffold every service in config
|
|
9
|
-
// node scripts/scaffold-sdk.js --service=people ← scaffold one service
|
|
10
|
-
// node scripts/scaffold-sdk.js --dry-run ← print what would be written
|
|
11
|
-
//
|
|
12
|
-
// Why scaffolding, not full codegen:
|
|
13
|
-
// The existing hand-written SDK modules (webhook, funnel, email, ...) embed
|
|
14
|
-
// real UX choices: `createEndpoint` not `postWebhookOrgsEndpoints`; `Delivery`
|
|
15
|
-
// not `WebhookDeliveryResponse`. A full codegen would either produce a worse
|
|
16
|
-
// surface (robot names) or require so much config to recover the hand-written
|
|
17
|
-
// shape that you might as well hand-write it. The middle ground: humans
|
|
18
|
-
// pick names/types; the script enforces the schema contract and emits the
|
|
19
|
-
// boilerplate function bodies + EXPOSES arrays.
|
|
20
|
-
//
|
|
21
|
-
// Future services scaffold cleanly: add an entry to SERVICES below, run the
|
|
22
|
-
// script, get a new file. Drift detection: re-running on a service whose
|
|
23
|
-
// schema endpoints changed will flag the diff before writing.
|
|
24
|
-
|
|
25
|
-
import * as fs from 'node:fs';
|
|
26
|
-
import * as path from 'node:path';
|
|
27
|
-
import { fileURLToPath } from 'node:url';
|
|
28
|
-
|
|
29
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
30
|
-
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
31
|
-
const SCHEMA_PATH = path.resolve(PKG_ROOT, '..', 'cli', 'schema-snapshot.json');
|
|
32
|
-
const OUT_DIR = path.resolve(PKG_ROOT, 'src');
|
|
33
|
-
|
|
34
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
35
|
-
// Service configs
|
|
36
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
37
|
-
//
|
|
38
|
-
// Each entry describes one SDK module. The script generates ONE file per entry
|
|
39
|
-
// at packages/sdk/src/<service>.ts. Add new services by appending to this list
|
|
40
|
-
// and re-running.
|
|
41
|
-
|
|
42
|
-
const SERVICES = [
|
|
43
|
-
{
|
|
44
|
-
service: 'people',
|
|
45
|
-
baseConst: 'PEOPLE_BASE',
|
|
46
|
-
sharedImports: [`import type { SearchFilter } from './audience';`],
|
|
47
|
-
types: `
|
|
48
|
-
export interface Location {
|
|
49
|
-
city?: string;
|
|
50
|
-
region?: string;
|
|
51
|
-
country?: string; // ISO 3166-1 alpha-2
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Goldfox-sourced person row. The data model is crawl-derived signals over
|
|
55
|
-
// scraped contact pages; rich on quality tiers and behavioral booleans,
|
|
56
|
-
// thin on legacy CRM fields (no title / function / industry as response
|
|
57
|
-
// columns — many of those moved to filterable inputs only).
|
|
58
|
-
export interface Person {
|
|
59
|
-
id: string;
|
|
60
|
-
full_name: string;
|
|
61
|
-
first_name?: string;
|
|
62
|
-
last_name?: string;
|
|
63
|
-
email?: string;
|
|
64
|
-
email_type?: string; // corporate | freemail | role_based | other_corporate
|
|
65
|
-
link_confidence?: number; // 0..1 — 1.0 = email-domain match (definitive)
|
|
66
|
-
location: Location;
|
|
67
|
-
company_id: string;
|
|
68
|
-
company: PersonCompany;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Person.company embeds the same shape returned by /company/search results.
|
|
72
|
-
// PersonCompany is an alias for Company — the company sub-object on a person
|
|
73
|
-
// row is identical to a standalone company row.
|
|
74
|
-
export interface PersonCompany {
|
|
75
|
-
id: string;
|
|
76
|
-
domain?: string;
|
|
77
|
-
name: string;
|
|
78
|
-
general_phone?: string;
|
|
79
|
-
address?: string;
|
|
80
|
-
is_registered_entity: boolean;
|
|
81
|
-
confidence: 'high' | 'low' | 'very_low';
|
|
82
|
-
country?: string;
|
|
83
|
-
country_consistent: boolean;
|
|
84
|
-
tld_class?: string; // cctld | generic | vanity | low_trust | other
|
|
85
|
-
has_careers_page: boolean;
|
|
86
|
-
has_investors_page: boolean;
|
|
87
|
-
has_shop_page: boolean;
|
|
88
|
-
has_blog: boolean;
|
|
89
|
-
has_c_level: boolean;
|
|
90
|
-
has_decision_maker: boolean;
|
|
91
|
-
headcount_lower_bound: number;
|
|
92
|
-
org_breadth: number;
|
|
93
|
-
source_count: number;
|
|
94
|
-
subdomain_variety: number;
|
|
95
|
-
multilingual: boolean;
|
|
96
|
-
language_count: number;
|
|
97
|
-
location?: Location;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export interface PeopleSearchResult {
|
|
101
|
-
people: Person[];
|
|
102
|
-
total: number;
|
|
103
|
-
limit: number;
|
|
104
|
-
offset: number;
|
|
105
|
-
has_more: boolean;
|
|
106
|
-
}`,
|
|
107
|
-
operations: [
|
|
108
|
-
{ name: 'searchPeople', method: 'POST', path: '/people/orgs/{org_id}/search', body: 'filter: SearchFilter', returns: 'PeopleSearchResult' },
|
|
109
|
-
{ name: 'getPerson', method: 'GET', path: '/people/orgs/{org_id}/{person_id}', returns: 'Person' },
|
|
110
|
-
],
|
|
111
|
-
},
|
|
112
|
-
|
|
113
|
-
{
|
|
114
|
-
service: 'company',
|
|
115
|
-
baseConst: 'COMPANY_BASE',
|
|
116
|
-
sharedImports: [
|
|
117
|
-
`import type { SearchFilter } from './audience';`,
|
|
118
|
-
`import type { Person } from './people';`,
|
|
119
|
-
],
|
|
120
|
-
types: `
|
|
121
|
-
import type { Location, PersonCompany } from './people';
|
|
122
|
-
|
|
123
|
-
// Company is the same shape as PersonCompany (a person's embedded company)
|
|
124
|
-
// with an optional embedded people array when include_people=N is passed.
|
|
125
|
-
export interface Company extends PersonCompany {
|
|
126
|
-
people?: Person[]; // present when include_people=N is passed
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export interface CompanySearchResult {
|
|
130
|
-
companies: Company[];
|
|
131
|
-
total: number;
|
|
132
|
-
limit: number;
|
|
133
|
-
offset: number;
|
|
134
|
-
has_more: boolean;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export interface CompanySearchOptions extends SearchFilter {
|
|
138
|
-
include_people?: number; // 0..10; embed up to N people per company
|
|
139
|
-
}`,
|
|
140
|
-
operations: [
|
|
141
|
-
{ name: 'searchCompanies', method: 'POST', path: '/company/orgs/{org_id}/search', body: 'options: CompanySearchOptions', returns: 'CompanySearchResult' },
|
|
142
|
-
{ name: 'getCompany', method: 'GET', path: '/company/orgs/{org_id}/{company_id}', query: 'include_people?: number', returns: 'Company' },
|
|
143
|
-
],
|
|
144
|
-
},
|
|
145
|
-
|
|
146
|
-
{
|
|
147
|
-
service: 'audience',
|
|
148
|
-
baseConst: 'AUDIENCE_BASE',
|
|
149
|
-
sharedImports: [
|
|
150
|
-
`import type { Person } from './people';`,
|
|
151
|
-
`import type { Company } from './company';`,
|
|
152
|
-
],
|
|
153
|
-
types: `
|
|
154
|
-
// Canonical filter shape — shared across /people/search, /company/search,
|
|
155
|
-
// and embedded in saved audiences. Goldfox-sourced; OR within array, AND
|
|
156
|
-
// across fields. Empty filter = all rows in source with default high-only
|
|
157
|
-
// confidence.
|
|
158
|
-
//
|
|
159
|
-
// Some fields are people-source-only (seniority, email_type,
|
|
160
|
-
// min_link_confidence) and silently ignored by company-source contexts.
|
|
161
|
-
export interface SearchFilter {
|
|
162
|
-
// Confidence tier(s) — high (default, 96.4% of dataset, no UGC),
|
|
163
|
-
// low (UGC source — spot-check), very_low (UGC + freemail mismatch).
|
|
164
|
-
// Pass explicit array to widen beyond the default high-only.
|
|
165
|
-
confidence?: ('high' | 'low' | 'very_low')[];
|
|
166
|
-
|
|
167
|
-
// ISO 3166-1 alpha-2 country code(s). Consensus across TLD + address + phone.
|
|
168
|
-
country?: string[];
|
|
169
|
-
|
|
170
|
-
// Only rows where TLD + address + phone all agree on country.
|
|
171
|
-
country_consistent?: boolean;
|
|
172
|
-
|
|
173
|
-
// Email_type — corporate / freemail / role_based / other_corporate.
|
|
174
|
-
// (Ignored on /company/search.)
|
|
175
|
-
email_type?: ('corporate' | 'freemail' | 'role_based' | 'other_corporate')[];
|
|
176
|
-
|
|
177
|
-
// Behavioral booleans — derived from page crawl signals.
|
|
178
|
-
has_c_level?: boolean; // ≥1 person with seniority = c_level
|
|
179
|
-
has_careers_page?: boolean; // /careers, /jobs, etc. — growth-stage proxy
|
|
180
|
-
has_decision_maker?: boolean; // ≥1 person with seniority ∈ (c_level, vp_director)
|
|
181
|
-
has_investors_page?: boolean; // /investors, /ir/ — mature-org proxy
|
|
182
|
-
has_shop_page?: boolean; // /shop, /store, /products — e-commerce proxy
|
|
183
|
-
|
|
184
|
-
// companies[0].legal_form_country is not null — name has a recognised
|
|
185
|
-
// legal suffix (GmbH/Ltd/Inc/SARL/...).
|
|
186
|
-
is_registered_entity?: boolean;
|
|
187
|
-
|
|
188
|
-
// Case-insensitive substring match on the row's domain.
|
|
189
|
-
keyword?: string;
|
|
190
|
-
|
|
191
|
-
// Pagination
|
|
192
|
-
limit?: number; // default 20, max 100
|
|
193
|
-
offset?: number;
|
|
194
|
-
|
|
195
|
-
// Row's headcount_lower_bound (≈ ARRAY_LENGTH(people)) must be ≥ this.
|
|
196
|
-
min_headcount?: number;
|
|
197
|
-
|
|
198
|
-
// Drop people whose link_confidence is below this threshold (0–1).
|
|
199
|
-
// 1.0 = email-domain match (definitive); 0.5 = strong; lower = weak.
|
|
200
|
-
// (Ignored on /company/search.)
|
|
201
|
-
min_link_confidence?: number;
|
|
202
|
-
|
|
203
|
-
// Row must appear in at least this many distinct source URLs.
|
|
204
|
-
min_source_count?: number;
|
|
205
|
-
|
|
206
|
-
// Goldfox seniority — c_level, vp_director, manager, senior_ic, ic.
|
|
207
|
-
// (Ignored on /company/search.)
|
|
208
|
-
seniority?: ('c_level' | 'vp_director' | 'manager' | 'senior_ic' | 'ic')[];
|
|
209
|
-
|
|
210
|
-
// TLD class — cctld / generic / vanity / low_trust / other.
|
|
211
|
-
tld_class?: ('cctld' | 'generic' | 'vanity' | 'low_trust' | 'other')[];
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export type AudienceSource = 'people' | 'company';
|
|
215
|
-
|
|
216
|
-
export interface Audience {
|
|
217
|
-
id: string;
|
|
218
|
-
org_id: string;
|
|
219
|
-
name: string;
|
|
220
|
-
description?: string;
|
|
221
|
-
source: AudienceSource;
|
|
222
|
-
filter: SearchFilter;
|
|
223
|
-
member_count: number;
|
|
224
|
-
created_at: string;
|
|
225
|
-
updated_at: string;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Backend returns 'people' or 'companies' keyed by source; the response
|
|
229
|
-
// itself does NOT carry the source field, so callers either know it from
|
|
230
|
-
// the audience object or detect by which array is non-empty.
|
|
231
|
-
export interface AudienceMembers {
|
|
232
|
-
total: number;
|
|
233
|
-
limit: number;
|
|
234
|
-
offset: number;
|
|
235
|
-
has_more: boolean;
|
|
236
|
-
people?: Person[];
|
|
237
|
-
companies?: Company[];
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
export interface AudienceRefreshResult {
|
|
241
|
-
total: number; // new member count
|
|
242
|
-
previous: number; // count before refresh
|
|
243
|
-
delta: number; // total - previous
|
|
244
|
-
refreshed_at: string;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
export interface CreateAudienceInput {
|
|
248
|
-
name: string;
|
|
249
|
-
source: AudienceSource;
|
|
250
|
-
filter: SearchFilter;
|
|
251
|
-
description?: string;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
export interface UpdateAudienceInput {
|
|
255
|
-
name?: string;
|
|
256
|
-
description?: string;
|
|
257
|
-
filter?: SearchFilter;
|
|
258
|
-
}`,
|
|
259
|
-
operations: [
|
|
260
|
-
{ name: 'createAudience', method: 'POST', path: '/audience/orgs/{org_id}/audiences', body: 'input: CreateAudienceInput', returns: 'Audience' },
|
|
261
|
-
{ name: 'listAudiences', method: 'GET', path: '/audience/orgs/{org_id}/audiences', returns: 'Audience[]' },
|
|
262
|
-
{ name: 'getAudience', method: 'GET', path: '/audience/orgs/{org_id}/audiences/{audience_id}', returns: 'Audience' },
|
|
263
|
-
{ name: 'updateAudience', method: 'PATCH', path: '/audience/orgs/{org_id}/audiences/{audience_id}', body: 'patch: UpdateAudienceInput', returns: 'Audience' },
|
|
264
|
-
{ name: 'deleteAudience', method: 'DELETE', path: '/audience/orgs/{org_id}/audiences/{audience_id}', returns: 'void' },
|
|
265
|
-
{ name: 'getAudienceMembers', method: 'GET', path: '/audience/orgs/{org_id}/audiences/{audience_id}/members', query: 'limit?: number; offset?: number', returns: 'AudienceMembers' },
|
|
266
|
-
{ name: 'refreshAudience', method: 'POST', path: '/audience/orgs/{org_id}/audiences/{audience_id}/refresh', returns: 'AudienceRefreshResult' },
|
|
267
|
-
],
|
|
268
|
-
},
|
|
269
|
-
|
|
270
|
-
{
|
|
271
|
-
service: 'llm',
|
|
272
|
-
baseConst: 'LLM_BASE',
|
|
273
|
-
types: `
|
|
274
|
-
export type Role = 'system' | 'user' | 'assistant';
|
|
275
|
-
|
|
276
|
-
export interface Message {
|
|
277
|
-
role: Role;
|
|
278
|
-
content: string;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
export interface CompleteRequest {
|
|
282
|
-
model: string;
|
|
283
|
-
messages: Message[];
|
|
284
|
-
max_tokens?: number;
|
|
285
|
-
temperature?: number;
|
|
286
|
-
stop?: string[];
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
export interface CompleteResponse {
|
|
290
|
-
model: string;
|
|
291
|
-
content: string;
|
|
292
|
-
finish_reason: string; // 'stop' | 'length' | 'safety' | ...
|
|
293
|
-
usage: {
|
|
294
|
-
input_tokens: number;
|
|
295
|
-
output_tokens: number;
|
|
296
|
-
cost_usd: number;
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
export interface EmbedRequest {
|
|
301
|
-
model: string;
|
|
302
|
-
input: string | string[]; // single string or batch
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Always returned as an array — even for a single-string input you get a
|
|
306
|
-
// one-element array. Each element is the dense vector as a flat number[].
|
|
307
|
-
// Embed-only usage has no output_tokens.
|
|
308
|
-
export interface EmbedResponse {
|
|
309
|
-
model: string;
|
|
310
|
-
embeddings: number[][];
|
|
311
|
-
usage: {
|
|
312
|
-
input_tokens: number;
|
|
313
|
-
cost_usd: number;
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Catalog row. 'chat' models have context + output_per_1m; 'embed' models
|
|
318
|
-
// have dimensions instead. Pricing is per 1M tokens at upstream rates
|
|
319
|
-
// (we resell at-cost while building our own inference).
|
|
320
|
-
export interface Model {
|
|
321
|
-
id: string;
|
|
322
|
-
kind: 'chat' | 'embed';
|
|
323
|
-
context?: number;
|
|
324
|
-
dimensions?: number;
|
|
325
|
-
input_per_1m: number;
|
|
326
|
-
output_per_1m?: number;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
export interface ModelsResponse {
|
|
330
|
-
models: Model[];
|
|
331
|
-
}`,
|
|
332
|
-
operations: [
|
|
333
|
-
{ name: 'complete', method: 'POST', path: '/llm/orgs/{org_id}/complete', body: 'req: CompleteRequest', returns: 'CompleteResponse' },
|
|
334
|
-
{ name: 'embed', method: 'POST', path: '/llm/orgs/{org_id}/embed', body: 'req: EmbedRequest', returns: 'EmbedResponse' },
|
|
335
|
-
{ name: 'listModels', method: 'GET', path: '/llm/orgs/{org_id}/models', returns: 'ModelsResponse' },
|
|
336
|
-
],
|
|
337
|
-
},
|
|
338
|
-
|
|
339
|
-
];
|
|
340
|
-
|
|
341
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
342
|
-
// Generator
|
|
343
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
344
|
-
|
|
345
|
-
function parseArgs(argv) {
|
|
346
|
-
const out = { service: null, dryRun: false, help: false };
|
|
347
|
-
for (const a of argv) {
|
|
348
|
-
if (a === '--help' || a === '-h') out.help = true;
|
|
349
|
-
else if (a === '--dry-run') out.dryRun = true;
|
|
350
|
-
else if (a.startsWith('--service=')) out.service = a.slice('--service='.length);
|
|
351
|
-
}
|
|
352
|
-
return out;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function loadSchema() {
|
|
356
|
-
if (!fs.existsSync(SCHEMA_PATH)) {
|
|
357
|
-
throw new Error(`Schema snapshot not found at ${SCHEMA_PATH}. Run: cd packages/cli && npm run update-schema`);
|
|
358
|
-
}
|
|
359
|
-
return JSON.parse(fs.readFileSync(SCHEMA_PATH, 'utf-8'));
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// Path params: /a/{x}/b/{y} → ['x', 'y']
|
|
363
|
-
function extractPathParams(p) {
|
|
364
|
-
const out = [];
|
|
365
|
-
const re = /\{([^}]+)\}/g;
|
|
366
|
-
let m;
|
|
367
|
-
while ((m = re.exec(p)) !== null) out.push(m[1]);
|
|
368
|
-
return out;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// snake_case → camelCase, used for path-param variable names + arg names.
|
|
372
|
-
function snakeToCamel(s) {
|
|
373
|
-
return s.replace(/_(.)/g, (_, c) => c.toUpperCase());
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// Build the function body with proper interpolated path and request(...) call.
|
|
377
|
-
function buildFunction(op, baseConst) {
|
|
378
|
-
const pathParams = extractPathParams(op.path);
|
|
379
|
-
// org_id is always the first path param by convention; surface as orgId for the function arg.
|
|
380
|
-
const argList = ['apiKey: string'];
|
|
381
|
-
const pathExprParts = []; // pieces to concatenate inside the template literal
|
|
382
|
-
|
|
383
|
-
// Walk the path and build the template literal substituting {param} → ${encodeURIComponent(varName)}
|
|
384
|
-
let interpolated = op.path.replace(/\{([^}]+)\}/g, (_, name) => {
|
|
385
|
-
const camel = snakeToCamel(name);
|
|
386
|
-
return '${encodeURIComponent(' + camel + ')}';
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
// Function args, in path-param order
|
|
390
|
-
for (const p of pathParams) argList.push(`${snakeToCamel(p)}: string`);
|
|
391
|
-
|
|
392
|
-
// Body (if any)
|
|
393
|
-
if (op.body) argList.push(op.body);
|
|
394
|
-
|
|
395
|
-
// Query (if any) — keep distinct from body
|
|
396
|
-
if (op.query) argList.push(op.query.split(';').length === 1
|
|
397
|
-
? op.query
|
|
398
|
-
: `opts: { ${op.query} }`);
|
|
399
|
-
|
|
400
|
-
// Build query string segment if needed
|
|
401
|
-
let urlExpr = '`' + '${' + baseConst + '}' + interpolated + '`';
|
|
402
|
-
let querySerializer = '';
|
|
403
|
-
if (op.query) {
|
|
404
|
-
// Simple shape — only handles `name?: type` (one param) or `{ a?: t; b?: t }` (multi)
|
|
405
|
-
const isMulti = op.query.includes(';');
|
|
406
|
-
if (isMulti) {
|
|
407
|
-
querySerializer = `
|
|
408
|
-
const _q = new URLSearchParams();
|
|
409
|
-
for (const [k, v] of Object.entries(opts)) {
|
|
410
|
-
if (v != null) _q.append(k, String(v));
|
|
411
|
-
}
|
|
412
|
-
const _qs = _q.toString();
|
|
413
|
-
const _url = _qs ? \`${urlExpr.slice(1, -1)}?\${_qs}\` : ${urlExpr};`;
|
|
414
|
-
urlExpr = '_url';
|
|
415
|
-
} else {
|
|
416
|
-
// Single optional query param: name?: type
|
|
417
|
-
const paramName = op.query.replace(/[?:].*/, '').trim();
|
|
418
|
-
querySerializer = `
|
|
419
|
-
const _url = ${paramName} != null
|
|
420
|
-
? \`${urlExpr.slice(1, -1)}?${paramName}=\${encodeURIComponent(String(${paramName}))}\`
|
|
421
|
-
: ${urlExpr};`;
|
|
422
|
-
urlExpr = '_url';
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
const bodyArg = op.body ? `, ${op.body.split(':')[0].trim()}` : '';
|
|
427
|
-
const returnsVoid = op.returns === 'void';
|
|
428
|
-
|
|
429
|
-
const sig = `export async function ${op.name}(${argList.join(', ')}): Promise<${op.returns}> {`;
|
|
430
|
-
const body = [
|
|
431
|
-
querySerializer,
|
|
432
|
-
` return request('${op.method}', ${urlExpr}, apiKey${bodyArg});`,
|
|
433
|
-
].filter(Boolean).join('\n').replace(/^\n/, '');
|
|
434
|
-
|
|
435
|
-
return `${sig}\n${body}\n}`;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
function renderModule(svc, schema) {
|
|
439
|
-
// Validate each operation's endpoint exists in the schema (drift detection).
|
|
440
|
-
for (const op of svc.operations) {
|
|
441
|
-
const pathItem = schema.paths[op.path];
|
|
442
|
-
if (!pathItem) throw new Error(`${svc.service}: schema has no path "${op.path}"`);
|
|
443
|
-
if (!pathItem[op.method.toLowerCase()]) {
|
|
444
|
-
throw new Error(`${svc.service}: schema path "${op.path}" has no method ${op.method}`);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
const exposes = svc.operations.map(op => `'${op.method} ${op.path}'`).join(',\n ');
|
|
449
|
-
const functions = svc.operations.map(op => buildFunction(op, 'BASE_URL')).join('\n\n');
|
|
450
|
-
const sharedImports = (svc.sharedImports || []).join('\n');
|
|
451
|
-
|
|
452
|
-
return `// Scaffolded from schema by scripts/scaffold-sdk.js — DO NOT remove the
|
|
453
|
-
// EXPOSES array (the coverage tool verifies it against the live schema).
|
|
454
|
-
// Types and function names ARE editable; re-running the scaffolder against
|
|
455
|
-
// a service whose config has been changed will diff vs. existing file
|
|
456
|
-
// (use --dry-run to inspect).
|
|
457
|
-
|
|
458
|
-
import { request } from './client';
|
|
459
|
-
import { ${svc.baseConst} as BASE_URL } from './config';
|
|
460
|
-
import type { Exposes } from './exposes';
|
|
461
|
-
${sharedImports ? '\n' + sharedImports + '\n' : ''}
|
|
462
|
-
export const EXPOSES: Exposes = [
|
|
463
|
-
${exposes},
|
|
464
|
-
];
|
|
465
|
-
${svc.types}
|
|
466
|
-
|
|
467
|
-
${functions}
|
|
468
|
-
`;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
function main() {
|
|
472
|
-
const args = parseArgs(process.argv.slice(2));
|
|
473
|
-
if (args.help) {
|
|
474
|
-
console.log(`Usage: scaffold-sdk [--service=<name>] [--dry-run]`);
|
|
475
|
-
process.exit(0);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
const schema = loadSchema();
|
|
479
|
-
const services = args.service
|
|
480
|
-
? SERVICES.filter(s => s.service === args.service)
|
|
481
|
-
: SERVICES;
|
|
482
|
-
|
|
483
|
-
if (services.length === 0) {
|
|
484
|
-
console.error(`Unknown service: ${args.service}. Known: ${SERVICES.map(s => s.service).join(', ')}`);
|
|
485
|
-
process.exit(1);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
for (const svc of services) {
|
|
489
|
-
const out = renderModule(svc, schema);
|
|
490
|
-
const file = path.join(OUT_DIR, `${svc.service}.ts`);
|
|
491
|
-
if (args.dryRun) {
|
|
492
|
-
console.log(`──── ${file} ────`);
|
|
493
|
-
console.log(out);
|
|
494
|
-
} else {
|
|
495
|
-
fs.writeFileSync(file, out, 'utf-8');
|
|
496
|
-
console.log(`✓ wrote ${path.relative(process.cwd(), file)} (${svc.operations.length} operations)`);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
main();
|
package/src/audience.ts
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
// Scaffolded from schema by scripts/scaffold-sdk.js — DO NOT remove the
|
|
2
|
-
// EXPOSES array (the coverage tool verifies it against the live schema).
|
|
3
|
-
// Types and function names ARE editable; re-running the scaffolder against
|
|
4
|
-
// a service whose config has been changed will diff vs. existing file
|
|
5
|
-
// (use --dry-run to inspect).
|
|
6
|
-
|
|
7
|
-
import { request } from './client';
|
|
8
|
-
import { AUDIENCE_BASE as BASE_URL } from './config';
|
|
9
|
-
import type { Exposes } from './exposes';
|
|
10
|
-
|
|
11
|
-
import type { Person } from './people';
|
|
12
|
-
import type { Company } from './company';
|
|
13
|
-
|
|
14
|
-
export const EXPOSES: Exposes = [
|
|
15
|
-
'POST /audience/orgs/{org_id}/audiences',
|
|
16
|
-
'GET /audience/orgs/{org_id}/audiences',
|
|
17
|
-
'GET /audience/orgs/{org_id}/audiences/{audience_id}',
|
|
18
|
-
'PATCH /audience/orgs/{org_id}/audiences/{audience_id}',
|
|
19
|
-
'DELETE /audience/orgs/{org_id}/audiences/{audience_id}',
|
|
20
|
-
'GET /audience/orgs/{org_id}/audiences/{audience_id}/members',
|
|
21
|
-
'POST /audience/orgs/{org_id}/audiences/{audience_id}/refresh',
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
// Canonical filter shape — shared across /people/search, /company/search,
|
|
25
|
-
// and embedded in saved audiences. Goldfox-sourced; OR within array, AND
|
|
26
|
-
// across fields. Empty filter = all rows in source with default high-only
|
|
27
|
-
// confidence.
|
|
28
|
-
//
|
|
29
|
-
// Some fields are people-source-only (seniority, email_type,
|
|
30
|
-
// min_link_confidence) and silently ignored by company-source contexts.
|
|
31
|
-
export interface SearchFilter {
|
|
32
|
-
// Confidence tier(s) — high (default, 96.4% of dataset, no UGC),
|
|
33
|
-
// low (UGC source — spot-check), very_low (UGC + freemail mismatch).
|
|
34
|
-
// Pass explicit array to widen beyond the default high-only.
|
|
35
|
-
confidence?: ('high' | 'low' | 'very_low')[];
|
|
36
|
-
|
|
37
|
-
// ISO 3166-1 alpha-2 country code(s). Consensus across TLD + address + phone.
|
|
38
|
-
country?: string[];
|
|
39
|
-
|
|
40
|
-
// Only rows where TLD + address + phone all agree on country.
|
|
41
|
-
country_consistent?: boolean;
|
|
42
|
-
|
|
43
|
-
// Email_type — corporate / freemail / role_based / other_corporate.
|
|
44
|
-
// (Ignored on /company/search.)
|
|
45
|
-
email_type?: ('corporate' | 'freemail' | 'role_based' | 'other_corporate')[];
|
|
46
|
-
|
|
47
|
-
// Behavioral booleans — derived from page crawl signals.
|
|
48
|
-
has_c_level?: boolean; // ≥1 person with seniority = c_level
|
|
49
|
-
has_careers_page?: boolean; // /careers, /jobs, etc. — growth-stage proxy
|
|
50
|
-
has_decision_maker?: boolean; // ≥1 person with seniority ∈ (c_level, vp_director)
|
|
51
|
-
has_investors_page?: boolean; // /investors, /ir/ — mature-org proxy
|
|
52
|
-
has_shop_page?: boolean; // /shop, /store, /products — e-commerce proxy
|
|
53
|
-
|
|
54
|
-
// companies[0].legal_form_country is not null — name has a recognised
|
|
55
|
-
// legal suffix (GmbH/Ltd/Inc/SARL/...).
|
|
56
|
-
is_registered_entity?: boolean;
|
|
57
|
-
|
|
58
|
-
// Case-insensitive substring match on the row's domain.
|
|
59
|
-
keyword?: string;
|
|
60
|
-
|
|
61
|
-
// Pagination
|
|
62
|
-
limit?: number; // default 20, max 100
|
|
63
|
-
offset?: number;
|
|
64
|
-
|
|
65
|
-
// Row's headcount_lower_bound (≈ ARRAY_LENGTH(people)) must be ≥ this.
|
|
66
|
-
min_headcount?: number;
|
|
67
|
-
|
|
68
|
-
// Drop people whose link_confidence is below this threshold (0–1).
|
|
69
|
-
// 1.0 = email-domain match (definitive); 0.5 = strong; lower = weak.
|
|
70
|
-
// (Ignored on /company/search.)
|
|
71
|
-
min_link_confidence?: number;
|
|
72
|
-
|
|
73
|
-
// Row must appear in at least this many distinct source URLs.
|
|
74
|
-
min_source_count?: number;
|
|
75
|
-
|
|
76
|
-
// Goldfox seniority — c_level, vp_director, manager, senior_ic, ic.
|
|
77
|
-
// (Ignored on /company/search.)
|
|
78
|
-
seniority?: ('c_level' | 'vp_director' | 'manager' | 'senior_ic' | 'ic')[];
|
|
79
|
-
|
|
80
|
-
// TLD class — cctld / generic / vanity / low_trust / other.
|
|
81
|
-
tld_class?: ('cctld' | 'generic' | 'vanity' | 'low_trust' | 'other')[];
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export type AudienceSource = 'people' | 'company';
|
|
85
|
-
|
|
86
|
-
export interface Audience {
|
|
87
|
-
id: string;
|
|
88
|
-
org_id: string;
|
|
89
|
-
name: string;
|
|
90
|
-
description?: string;
|
|
91
|
-
source: AudienceSource;
|
|
92
|
-
filter: SearchFilter;
|
|
93
|
-
member_count: number;
|
|
94
|
-
created_at: string;
|
|
95
|
-
updated_at: string;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Backend returns 'people' or 'companies' keyed by source; the response
|
|
99
|
-
// itself does NOT carry the source field, so callers either know it from
|
|
100
|
-
// the audience object or detect by which array is non-empty.
|
|
101
|
-
export interface AudienceMembers {
|
|
102
|
-
total: number;
|
|
103
|
-
limit: number;
|
|
104
|
-
offset: number;
|
|
105
|
-
has_more: boolean;
|
|
106
|
-
people?: Person[];
|
|
107
|
-
companies?: Company[];
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export interface AudienceRefreshResult {
|
|
111
|
-
total: number; // new member count
|
|
112
|
-
previous: number; // count before refresh
|
|
113
|
-
delta: number; // total - previous
|
|
114
|
-
refreshed_at: string;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export interface CreateAudienceInput {
|
|
118
|
-
name: string;
|
|
119
|
-
source: AudienceSource;
|
|
120
|
-
filter: SearchFilter;
|
|
121
|
-
description?: string;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export interface UpdateAudienceInput {
|
|
125
|
-
name?: string;
|
|
126
|
-
description?: string;
|
|
127
|
-
filter?: SearchFilter;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export async function createAudience(apiKey: string, orgId: string, input: CreateAudienceInput): Promise<Audience> {
|
|
131
|
-
return request('POST', `${BASE_URL}/audience/orgs/${encodeURIComponent(orgId)}/audiences`, apiKey, input);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export async function listAudiences(apiKey: string, orgId: string): Promise<Audience[]> {
|
|
135
|
-
return request('GET', `${BASE_URL}/audience/orgs/${encodeURIComponent(orgId)}/audiences`, apiKey);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export async function getAudience(apiKey: string, orgId: string, audienceId: string): Promise<Audience> {
|
|
139
|
-
return request('GET', `${BASE_URL}/audience/orgs/${encodeURIComponent(orgId)}/audiences/${encodeURIComponent(audienceId)}`, apiKey);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export async function updateAudience(apiKey: string, orgId: string, audienceId: string, patch: UpdateAudienceInput): Promise<Audience> {
|
|
143
|
-
return request('PATCH', `${BASE_URL}/audience/orgs/${encodeURIComponent(orgId)}/audiences/${encodeURIComponent(audienceId)}`, apiKey, patch);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export async function deleteAudience(apiKey: string, orgId: string, audienceId: string): Promise<void> {
|
|
147
|
-
return request('DELETE', `${BASE_URL}/audience/orgs/${encodeURIComponent(orgId)}/audiences/${encodeURIComponent(audienceId)}`, apiKey);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export async function getAudienceMembers(apiKey: string, orgId: string, audienceId: string, opts: { limit?: number; offset?: number } = {}): Promise<AudienceMembers> {
|
|
151
|
-
const _q = new URLSearchParams();
|
|
152
|
-
for (const [k, v] of Object.entries(opts)) {
|
|
153
|
-
if (v != null) _q.append(k, String(v));
|
|
154
|
-
}
|
|
155
|
-
const _qs = _q.toString();
|
|
156
|
-
const _url = _qs ? `${BASE_URL}/audience/orgs/${encodeURIComponent(orgId)}/audiences/${encodeURIComponent(audienceId)}/members?${_qs}` : `${BASE_URL}/audience/orgs/${encodeURIComponent(orgId)}/audiences/${encodeURIComponent(audienceId)}/members`;
|
|
157
|
-
return request('GET', _url, apiKey);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export async function refreshAudience(apiKey: string, orgId: string, audienceId: string): Promise<AudienceRefreshResult> {
|
|
161
|
-
return request('POST', `${BASE_URL}/audience/orgs/${encodeURIComponent(orgId)}/audiences/${encodeURIComponent(audienceId)}/refresh`, apiKey);
|
|
162
|
-
}
|