lumnisai 0.5.51 → 0.5.53
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/index.cjs +152 -12
- package/dist/index.d.cts +463 -4
- package/dist/index.d.mts +463 -4
- package/dist/index.d.ts +463 -4
- package/dist/index.mjs +152 -12
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -970,6 +970,7 @@ class ContactRelationshipsResource {
|
|
|
970
970
|
}
|
|
971
971
|
}
|
|
972
972
|
|
|
973
|
+
const COMPANY_SEARCH_PASSTHROUGH_KEYS = ["filters", "properties", "owners"];
|
|
973
974
|
class CrmResource {
|
|
974
975
|
constructor(http) {
|
|
975
976
|
this.http = http;
|
|
@@ -1069,6 +1070,134 @@ class CrmResource {
|
|
|
1069
1070
|
data
|
|
1070
1071
|
);
|
|
1071
1072
|
}
|
|
1073
|
+
/**
|
|
1074
|
+
* List the company fields the connected CRM exposes, with the operators each
|
|
1075
|
+
* one accepts — what a filter builder needs before calling
|
|
1076
|
+
* {@link searchCompanies}. A live, read-only provider call: nothing is
|
|
1077
|
+
* written to the CRM and nothing is served from the `crm_contacts` ledger.
|
|
1078
|
+
*
|
|
1079
|
+
* Pass `propertyName` to narrow to a single field. Attio returns
|
|
1080
|
+
* `select`/`status` choices only that way; HubSpot already includes
|
|
1081
|
+
* enumeration choices in the full listing, except for externally-sourced
|
|
1082
|
+
* ones, which come back as `options: null, optionsComplete: false`.
|
|
1083
|
+
*
|
|
1084
|
+
* Field metadata is provider-shaped, so the return type narrows on the
|
|
1085
|
+
* `provider` you pass.
|
|
1086
|
+
*
|
|
1087
|
+
* A definition with `references: 'owner'` holds a CRM user id; request it
|
|
1088
|
+
* in {@link searchCompanies} to have each company's `owners` map resolve
|
|
1089
|
+
* it to a name and email. HubSpot's is `hubspot_owner_id`; Attio has no
|
|
1090
|
+
* standard one, so take the actor-reference slug this listing flags.
|
|
1091
|
+
*
|
|
1092
|
+
* Failure modes: `403 crm_access_denied` and `503 crm_access_unavailable`
|
|
1093
|
+
* (the `crmUserId` grant), `409 crm_not_connected` (no single active
|
|
1094
|
+
* connection for the owner), `404 crm_property_not_found`,
|
|
1095
|
+
* `429 crm_rate_limited`, `504 crm_read_timeout`, `502 crm_read_failed`.
|
|
1096
|
+
*
|
|
1097
|
+
* @example
|
|
1098
|
+
* ```typescript
|
|
1099
|
+
* const { properties } = await client.crm.getCompanyProperties({
|
|
1100
|
+
* userId: 'user@example.com',
|
|
1101
|
+
* provider: 'hubspot',
|
|
1102
|
+
* })
|
|
1103
|
+
* const filterable = properties.filter(p => p.operators.length > 0)
|
|
1104
|
+
*
|
|
1105
|
+
* // Attio choices need the field named explicitly.
|
|
1106
|
+
* const tier = await client.crm.getCompanyProperties({
|
|
1107
|
+
* userId: 'user@example.com',
|
|
1108
|
+
* provider: 'attio',
|
|
1109
|
+
* propertyName: 'employee_range',
|
|
1110
|
+
* })
|
|
1111
|
+
* console.log(tier.properties[0].options)
|
|
1112
|
+
*
|
|
1113
|
+
* // The field that can fill an owner column.
|
|
1114
|
+
* const ownerField = properties.find(p => p.references === 'owner')
|
|
1115
|
+
* ```
|
|
1116
|
+
*/
|
|
1117
|
+
async getCompanyProperties(params) {
|
|
1118
|
+
const response = await this.http.get(
|
|
1119
|
+
"/crm/companies/properties",
|
|
1120
|
+
{
|
|
1121
|
+
// Backend requires snake_case query params (see getContactsSyncStatus).
|
|
1122
|
+
params: {
|
|
1123
|
+
user_id: params.userId,
|
|
1124
|
+
provider: params.provider,
|
|
1125
|
+
property_name: params.propertyName,
|
|
1126
|
+
crm_user_id: params.crmUserId
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
);
|
|
1130
|
+
return response;
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Read one page of companies from the connected CRM using that provider's
|
|
1134
|
+
* own filter syntax. Read-only: no CRM writes, no local persistence, so the
|
|
1135
|
+
* same request can be replayed to refresh a preview.
|
|
1136
|
+
*
|
|
1137
|
+
* Filters are never translated. HubSpot takes `filterGroups` (AND within a
|
|
1138
|
+
* group, OR between groups) with string comparison values; Attio takes its
|
|
1139
|
+
* record-query object with `$`-prefixed operators. Both cross the wire
|
|
1140
|
+
* verbatim — the SDK's camelCase ↔ snake_case conversion is switched off for
|
|
1141
|
+
* `filters` and for each company's `properties` and `owners` maps, whose
|
|
1142
|
+
* keys are CRM property names. Discover valid names and operators with
|
|
1143
|
+
* {@link getCompanyProperties}.
|
|
1144
|
+
*
|
|
1145
|
+
* Requesting an owner-typed field (`references: 'owner'` in its definition)
|
|
1146
|
+
* also resolves it: `owners[field]` is the CRM user's `{ id, name, email }`,
|
|
1147
|
+
* null when the company has none, with `name`/`email` null when the id is
|
|
1148
|
+
* not in the user directory. Resolution is part of the page — if the
|
|
1149
|
+
* directory read fails the whole request fails with the codes below rather
|
|
1150
|
+
* than returning half-resolved owners. Cost: any requested field adds one
|
|
1151
|
+
* definitions read per page; an owner field adds one directory read on top.
|
|
1152
|
+
*
|
|
1153
|
+
* Paging is cursor-based: pass the previous page's `nextCursor` back as
|
|
1154
|
+
* `cursor` and keep every other field identical, because HubSpot pages by
|
|
1155
|
+
* record id inside the original query. `nextCursor: null` is the last page.
|
|
1156
|
+
* `total` is reported on the first HubSpot page only, and is always null for
|
|
1157
|
+
* Attio.
|
|
1158
|
+
*
|
|
1159
|
+
* An empty `companies` array is a real "no matches" result — provider
|
|
1160
|
+
* failures raise instead. Failure modes: `403 crm_access_denied` and
|
|
1161
|
+
* `503 crm_access_unavailable` (the `crmUserId` grant),
|
|
1162
|
+
* `409 crm_not_connected`, `400 invalid_crm_filter` (the provider rejected
|
|
1163
|
+
* the query), `422 invalid_crm_request` (filter/limit/cursor budgets,
|
|
1164
|
+
* checked before the provider call), `429 crm_rate_limited`,
|
|
1165
|
+
* `504 crm_read_timeout`, `502 crm_read_failed`.
|
|
1166
|
+
*
|
|
1167
|
+
* @example
|
|
1168
|
+
* ```typescript
|
|
1169
|
+
* const request: CrmHubspotCompanySearchRequest = {
|
|
1170
|
+
* userId: 'user@example.com',
|
|
1171
|
+
* provider: 'hubspot',
|
|
1172
|
+
* filters: {
|
|
1173
|
+
* filterGroups: [{
|
|
1174
|
+
* filters: [
|
|
1175
|
+
* { propertyName: 'domain', operator: 'CONTAINS_TOKEN', value: 'acme' },
|
|
1176
|
+
* { propertyName: 'numberofemployees', operator: 'GT', value: '50' },
|
|
1177
|
+
* ],
|
|
1178
|
+
* }],
|
|
1179
|
+
* },
|
|
1180
|
+
* properties: ['numberofemployees', 'industry', 'hubspot_owner_id'],
|
|
1181
|
+
* limit: 50,
|
|
1182
|
+
* }
|
|
1183
|
+
*
|
|
1184
|
+
* const page = await client.crm.searchCompanies(request)
|
|
1185
|
+
* for (const company of page.companies)
|
|
1186
|
+
* console.log(company.name, company.properties.industry, company.owners?.hubspot_owner_id?.name)
|
|
1187
|
+
*
|
|
1188
|
+
* // Same query, next page.
|
|
1189
|
+
* if (page.nextCursor)
|
|
1190
|
+
* await client.crm.searchCompanies({ ...request, cursor: page.nextCursor })
|
|
1191
|
+
* ```
|
|
1192
|
+
*/
|
|
1193
|
+
async searchCompanies(data) {
|
|
1194
|
+
const response = await this.http.post(
|
|
1195
|
+
"/crm/companies/search",
|
|
1196
|
+
data,
|
|
1197
|
+
{ passthroughKeys: COMPANY_SEARCH_PASSTHROUGH_KEYS }
|
|
1198
|
+
);
|
|
1199
|
+
return response;
|
|
1200
|
+
}
|
|
1072
1201
|
/**
|
|
1073
1202
|
* Trigger a full mirror of the owner's CRM contact book into the local
|
|
1074
1203
|
* `crm_contacts` ledger. Returns immediately (`202`); poll
|
|
@@ -1950,23 +2079,28 @@ const PASSTHROUGH_VALUE_KEYS = /* @__PURE__ */ new Set([
|
|
|
1950
2079
|
// those would rename the customer's own groups.
|
|
1951
2080
|
"committee"
|
|
1952
2081
|
]);
|
|
1953
|
-
function convertCase(obj, converter) {
|
|
2082
|
+
function convertCase(obj, converter, passthroughKeys) {
|
|
1954
2083
|
if (Array.isArray(obj)) {
|
|
1955
|
-
return obj.map((v) => convertCase(v, converter));
|
|
2084
|
+
return obj.map((v) => convertCase(v, converter, passthroughKeys));
|
|
1956
2085
|
} else if (obj !== null && typeof obj === "object") {
|
|
1957
2086
|
return Object.keys(obj).reduce((acc, key) => {
|
|
1958
|
-
const value =
|
|
2087
|
+
const value = passthroughKeys.has(key) ? obj[key] : convertCase(obj[key], converter, passthroughKeys);
|
|
1959
2088
|
acc[converter(key)] = value;
|
|
1960
2089
|
return acc;
|
|
1961
2090
|
}, {});
|
|
1962
2091
|
}
|
|
1963
2092
|
return obj;
|
|
1964
2093
|
}
|
|
1965
|
-
function
|
|
1966
|
-
|
|
2094
|
+
function passthroughSet(extraKeys) {
|
|
2095
|
+
if (!extraKeys || extraKeys.length === 0)
|
|
2096
|
+
return PASSTHROUGH_VALUE_KEYS;
|
|
2097
|
+
return /* @__PURE__ */ new Set([...PASSTHROUGH_VALUE_KEYS, ...extraKeys]);
|
|
1967
2098
|
}
|
|
1968
|
-
function
|
|
1969
|
-
return convertCase(obj,
|
|
2099
|
+
function toCamelCase(obj, extraPassthroughKeys) {
|
|
2100
|
+
return convertCase(obj, toCamel, passthroughSet(extraPassthroughKeys));
|
|
2101
|
+
}
|
|
2102
|
+
function toSnakeCase(obj, extraPassthroughKeys) {
|
|
2103
|
+
return convertCase(obj, toSnake, passthroughSet(extraPassthroughKeys));
|
|
1970
2104
|
}
|
|
1971
2105
|
|
|
1972
2106
|
class MessagingResource {
|
|
@@ -5990,7 +6124,7 @@ class Http {
|
|
|
5990
6124
|
backoffFactor: options.backoffFactor || DEFAULT_BACKOFF_FACTOR
|
|
5991
6125
|
};
|
|
5992
6126
|
}
|
|
5993
|
-
async _handleResponse(response) {
|
|
6127
|
+
async _handleResponse(response, passthroughKeys) {
|
|
5994
6128
|
const requestId = response.headers.get("x-request-id");
|
|
5995
6129
|
if (response.ok) {
|
|
5996
6130
|
if (response.status === 204)
|
|
@@ -5998,7 +6132,7 @@ class Http {
|
|
|
5998
6132
|
const contentType = response.headers.get("content-type") || "";
|
|
5999
6133
|
if (contentType.includes("application/json")) {
|
|
6000
6134
|
const json = await response.json();
|
|
6001
|
-
return toCamelCase(json);
|
|
6135
|
+
return toCamelCase(json, passthroughKeys);
|
|
6002
6136
|
}
|
|
6003
6137
|
return await response.text();
|
|
6004
6138
|
}
|
|
@@ -6028,7 +6162,13 @@ class Http {
|
|
|
6028
6162
|
}
|
|
6029
6163
|
}
|
|
6030
6164
|
async request(path, init = {}) {
|
|
6031
|
-
const {
|
|
6165
|
+
const {
|
|
6166
|
+
body,
|
|
6167
|
+
params,
|
|
6168
|
+
idempotencyKey: idempotencyKeyOption,
|
|
6169
|
+
passthroughKeys,
|
|
6170
|
+
...fetchOptions
|
|
6171
|
+
} = init;
|
|
6032
6172
|
const method = fetchOptions.method || "GET";
|
|
6033
6173
|
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
6034
6174
|
const fullPath = this.options.apiPrefix ? `${this.options.apiPrefix}${normalizedPath}` : normalizedPath;
|
|
@@ -6057,10 +6197,10 @@ class Http {
|
|
|
6057
6197
|
...fetchOptions,
|
|
6058
6198
|
method,
|
|
6059
6199
|
headers,
|
|
6060
|
-
body: body ? JSON.stringify(toSnakeCase(body)) : void 0,
|
|
6200
|
+
body: body ? JSON.stringify(toSnakeCase(body, passthroughKeys)) : void 0,
|
|
6061
6201
|
signal: controller.signal
|
|
6062
6202
|
});
|
|
6063
|
-
return await this._handleResponse(response);
|
|
6203
|
+
return await this._handleResponse(response, passthroughKeys);
|
|
6064
6204
|
} catch (error) {
|
|
6065
6205
|
lastError = error;
|
|
6066
6206
|
if (error instanceof RateLimitError) {
|