@getvitops/utils 8.1.0 → 8.2.0-alpha.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +264 -8
  2. package/dist/{adc-BFrzpMHq.d.mts → adc-BoEoogyo.d.mts} +2 -3
  3. package/dist/adc-BoEoogyo.d.mts.map +1 -0
  4. package/dist/ads/index.d.mts +50 -17
  5. package/dist/ads/index.d.mts.map +1 -1
  6. package/dist/ads/index.mjs +75 -7
  7. package/dist/ads/index.mjs.map +1 -1
  8. package/dist/cloudflare-BiK61Oq4.d.mts.map +1 -1
  9. package/dist/color/index.d.mts +34 -35
  10. package/dist/color/index.d.mts.map +1 -1
  11. package/dist/color/index.mjs +3 -1
  12. package/dist/color/index.mjs.map +1 -1
  13. package/dist/crm/index.d.mts +288 -0
  14. package/dist/crm/index.d.mts.map +1 -0
  15. package/dist/crm/index.mjs +731 -0
  16. package/dist/crm/index.mjs.map +1 -0
  17. package/dist/email/index.d.mts +216 -0
  18. package/dist/email/index.d.mts.map +1 -0
  19. package/dist/email/index.mjs +331 -0
  20. package/dist/email/index.mjs.map +1 -0
  21. package/dist/favicon.d.mts +8 -9
  22. package/dist/favicon.d.mts.map +1 -1
  23. package/dist/git/index.d.mts +65 -0
  24. package/dist/git/index.d.mts.map +1 -0
  25. package/dist/git/index.mjs +107 -0
  26. package/dist/git/index.mjs.map +1 -0
  27. package/dist/hosting/index.d.mts +29 -29
  28. package/dist/hosting/index.d.mts.map +1 -1
  29. package/dist/hosting/index.mjs +7 -4
  30. package/dist/hosting/index.mjs.map +1 -1
  31. package/dist/identity-BoRddTx3.mjs +35 -0
  32. package/dist/identity-BoRddTx3.mjs.map +1 -0
  33. package/dist/index.d.mts +24 -25
  34. package/dist/index.d.mts.map +1 -1
  35. package/dist/index.mjs +5 -5
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/indexing/index.d.mts +23 -24
  38. package/dist/indexing/index.d.mts.map +1 -1
  39. package/dist/media/index.d.mts +24 -24
  40. package/dist/media/index.d.mts.map +1 -1
  41. package/dist/media/index.mjs +3 -3
  42. package/dist/media/index.mjs.map +1 -1
  43. package/dist/notify/index.d.mts +26 -15
  44. package/dist/notify/index.d.mts.map +1 -1
  45. package/dist/notify/index.mjs +50 -20
  46. package/dist/notify/index.mjs.map +1 -1
  47. package/dist/onboarding/index.d.mts +10 -141
  48. package/dist/onboarding/index.d.mts.map +1 -1
  49. package/dist/onboarding/index.mjs +1 -144
  50. package/dist/onboarding/index.mjs.map +1 -1
  51. package/dist/plan-BLKikoOG.mjs +150 -0
  52. package/dist/plan-BLKikoOG.mjs.map +1 -0
  53. package/dist/plan-Hq8Pb0Uw.d.mts +133 -0
  54. package/dist/plan-Hq8Pb0Uw.d.mts.map +1 -0
  55. package/dist/reviews/index.d.mts +12 -13
  56. package/dist/reviews/index.d.mts.map +1 -1
  57. package/dist/schema/index.d.mts +7 -7
  58. package/dist/schema/index.d.mts.map +1 -1
  59. package/dist/security/index.d.mts +2 -2
  60. package/dist/security/index.d.mts.map +1 -1
  61. package/dist/{token-Bz_-igw-.d.mts → token-DmmoQ7um.d.mts} +6 -3
  62. package/dist/token-DmmoQ7um.d.mts.map +1 -0
  63. package/dist/tracking/index.d.mts +43 -17
  64. package/dist/tracking/index.d.mts.map +1 -1
  65. package/dist/tracking/index.mjs +2 -1
  66. package/dist/tracking/index.mjs.map +1 -1
  67. package/dist/{types-CHHjzRrv.d.mts → types-7ELci8RQ.d.mts} +4 -2
  68. package/dist/types-7ELci8RQ.d.mts.map +1 -0
  69. package/package.json +14 -4
  70. package/dist/adc-BFrzpMHq.d.mts.map +0 -1
  71. package/dist/token-Bz_-igw-.d.mts.map +0 -1
  72. package/dist/types-CHHjzRrv.d.mts.map +0 -1
@@ -0,0 +1,288 @@
1
+ import { t as ConversionEvent } from "../types-7ELci8RQ.mjs";
2
+ //#region src/crm/types.d.ts
3
+ /**
4
+ * The CRM module's own option surface.
5
+ *
6
+ * Deliberately structural rather than `Config['site']['crm']` imported from
7
+ * `@getvitops/generator`: the generator already depends on this package, so
8
+ * importing back would be a cycle. The vocabulary mirrors that block field for
9
+ * field — the same arrangement, and for the same reason, as `IndexingConfig`
10
+ * and `NotifyContext` beside it.
11
+ *
12
+ * Optional fields are written `?: T | undefined` throughout: `exactOptionalPropertyTypes`
13
+ * is on, and these describe a *parsed JSON config* where an absent key and an
14
+ * explicit `undefined` are the same thing.
15
+ */
16
+ type CrmProvider = 'hubspot' | 'bigin' | 'jobber';
17
+ /** Human-readable label per provider, for the notification email's Delivery
18
+ * line (`describeCrmResult`) and anywhere else a provider must be named
19
+ * without a hand-written ternary. Exhaustive by type — a provider missing
20
+ * here is a compile error, not a mislabelled email. */
21
+ export declare const PROVIDER_LABELS: Record<CrmProvider, string>;
22
+ /** Bigin's data centre — the region a self-client's tokens and API host belong to. */
23
+ type BiginDataCenter = 'com' | 'ca' | 'eu' | 'in' | 'com.au' | 'jp';
24
+ interface HubSpotConfig {
25
+ portalId: string;
26
+ formGuid: string;
27
+ /** Default `na`. Only affects the submissions API host. */
28
+ dataCenter?: 'na' | 'eu' | undefined;
29
+ }
30
+ interface BiginPipelineConfig {
31
+ enabled: boolean;
32
+ subPipeline?: string | undefined;
33
+ stage?: string | undefined;
34
+ }
35
+ interface BiginConfig {
36
+ dataCenter: BiginDataCenter;
37
+ /** Default `Email` — Bigin's own unique field, used for `upsert`'s dedupe. */
38
+ duplicateCheckField?: string | undefined;
39
+ pipeline?: BiginPipelineConfig | undefined;
40
+ }
41
+ interface JobberConfig {
42
+ /**
43
+ * The pinned `X-JOBBER-GRAPHQL-VERSION` date this integration was built and
44
+ * tested against. Overriding it is an escape hatch for a client's app
45
+ * pinned to a different version — not something to reach for casually,
46
+ * since Jobber's schema can change field names between versions.
47
+ */
48
+ apiVersion?: string | undefined;
49
+ /**
50
+ * Look up an existing client by email before creating one (default
51
+ * **false**). Jobber's `clientCreate` has no upsert, so a resubmitting
52
+ * visitor creates a second client unless this runs — but the lookup query's
53
+ * filter shape is not yet verified against a live Jobber schema (see
54
+ * `jobber.ts`'s docblock), so it defaults off rather than risk a wrong
55
+ * query silently blocking a real submission. Set `true` once verified for
56
+ * your account.
57
+ */
58
+ dedupe?: boolean | undefined;
59
+ note?: {
60
+ /** Attach a best-effort note carrying the message + unmapped
61
+ * attribution (default true). A failure here never downgrades the
62
+ * client write's own success — see `jobber.ts`. */
63
+ enabled?: boolean | undefined;
64
+ } | undefined;
65
+ }
66
+ interface CrmConfig {
67
+ provider: CrmProvider;
68
+ enabled?: boolean | undefined;
69
+ hubspot?: HubSpotConfig | undefined;
70
+ bigin?: BiginConfig | undefined;
71
+ jobber?: JobberConfig | undefined;
72
+ /** Submitted field name → CRM property name. Unmapped fields are dropped, not guessed. */
73
+ fieldMap?: Record<string, string> | undefined;
74
+ /** `_ac` tracking key → CRM property name. Same drop-if-unmapped rule. */
75
+ trackingFields?: Record<string, string> | undefined;
76
+ }
77
+ /** Credentials, never config — always injected by the caller from env vars. */
78
+ interface HubSpotCredentials {
79
+ accessToken: string;
80
+ }
81
+ interface BiginCredentials {
82
+ clientId: string;
83
+ clientSecret: string;
84
+ refreshToken: string;
85
+ }
86
+ /**
87
+ * Jobber's self-client OAuth credential. The refresh token is expected to be
88
+ * static — Refresh Token Rotation must be OFF in the app's Developer Center
89
+ * settings, since a Worker reading a static env var cannot persist a rotated
90
+ * one. See `jobber-token.ts` for the detection when a client's app has it on
91
+ * anyway.
92
+ */
93
+ interface JobberCredentials {
94
+ clientId: string;
95
+ clientSecret: string;
96
+ refreshToken: string;
97
+ }
98
+ type CrmCredentials = {
99
+ provider: 'hubspot';
100
+ hubspot: HubSpotCredentials;
101
+ } | {
102
+ provider: 'bigin';
103
+ bigin: BiginCredentials;
104
+ } | {
105
+ provider: 'jobber';
106
+ jobber: JobberCredentials;
107
+ };
108
+ /** A channel's decision: on, or off with a stated reason. Mirrors `ChannelPlan`. */
109
+ interface CrmPlan {
110
+ enabled: boolean;
111
+ provider?: CrmProvider | undefined;
112
+ /** Why it's off. Present iff `enabled` is false. */
113
+ skip?: string | undefined;
114
+ }
115
+ /** A provider-neutral record — what a submission maps to, before either executor
116
+ * renders it into that CRM's own request shape. */
117
+ interface CrmRecord {
118
+ firstName?: string | undefined;
119
+ lastName?: string | undefined;
120
+ email?: string | undefined;
121
+ phone?: string | undefined;
122
+ company?: string | undefined;
123
+ message?: string | undefined;
124
+ /** Mapped fields beyond the identity ones above, keyed by the CRM property name. */
125
+ extra: Record<string, string>;
126
+ /** `trackingFields`-mapped attribution, keyed by the CRM property name. Empty
127
+ * when the visitor arrived with no `_ac` cookie — never an error. */
128
+ attribution: Record<string, string>;
129
+ }
130
+ /**
131
+ * The outcome of one delivery attempt. Never thrown — a CRM write is
132
+ * best-effort and its failure is a fact for the caller to log or notify with,
133
+ * not an exception to propagate into the request that produced the submission.
134
+ */
135
+ interface CrmResult {
136
+ ok: boolean;
137
+ provider: CrmProvider;
138
+ /** The created/updated record's id, when the CRM returned one. */
139
+ recordId?: string | undefined;
140
+ /** Why it didn't write, or what the classification of the failure was. */
141
+ reason?: string | undefined;
142
+ /** The provider's own error/status code, when it gave one. */
143
+ code?: string | undefined;
144
+ }
145
+ //#endregion
146
+ //#region src/crm/hubspot.d.ts
147
+ interface HubSpotSubmitContext {
148
+ hutk?: string | undefined;
149
+ pageUri?: string | undefined;
150
+ pageName?: string | undefined;
151
+ }
152
+ /**
153
+ * Submit one record to HubSpot's secure forms endpoint.
154
+ *
155
+ * Never throws — a network failure or an unparsable response both come back
156
+ * as a `CrmResult` with `ok: false`, the same "structured result, not an
157
+ * exception" rule `security/turnstile.ts` and `indexing/gsc.ts` follow. Status
158
+ * classification: 401 is a rejected/expired token; 400 naming
159
+ * `MISSING_REQUIRED_FIELDS` or an unrecognised field name is a mapping
160
+ * problem (`fieldMap`/`trackingFields` naming a property that isn't on the
161
+ * form); 429 is the per-portal rate limit.
162
+ */
163
+ export declare function submitToHubSpot(record: CrmRecord, config: HubSpotConfig, credentials: HubSpotCredentials, context?: HubSpotSubmitContext, fetchImpl?: typeof fetch, timeoutMs?: number): Promise<CrmResult>;
164
+ //#endregion
165
+ //#region src/crm/map.d.ts
166
+ /**
167
+ * Bigin requires `Last_Name`; a form that only asked for a full name or an
168
+ * email needs one derived rather than 400ing on insert.
169
+ *
170
+ * The fallback chain, in order: an explicit last name field → the last space-
171
+ * separated token of a full name → the local part of the email address →
172
+ * refuse, with a stated reason, rather than fabricate a name from nothing.
173
+ * `map.ts` never emits an empty `lastName` for Bigin — `deliverToCrm` reads
174
+ * the refusal and skips the write instead of letting the CRM reject it.
175
+ */
176
+ export declare function deriveLastName(identity: {
177
+ firstName?: string | undefined;
178
+ lastName?: string | undefined;
179
+ fullName: string;
180
+ email?: string | undefined;
181
+ }): {
182
+ lastName: string;
183
+ } | {
184
+ refusal: string;
185
+ };
186
+ export declare function mapSubmission(event: ConversionEvent, config: CrmConfig): CrmRecord;
187
+ //#endregion
188
+ //#region src/crm/plan.d.ts
189
+ export declare function planCrm(config: CrmConfig | undefined, credentials: CrmCredentials | undefined): CrmPlan;
190
+ //#endregion
191
+ //#region src/crm/bigin.d.ts
192
+ export declare function zohoApiHost(dataCenter: string): string;
193
+ /**
194
+ * Write one record to Bigin. Never throws — a token-exchange failure, a
195
+ * network error and a rejected response all come back as `CrmResult`.
196
+ */
197
+ export declare function submitToBigin(record: CrmRecord, config: BiginConfig, credentials: BiginCredentials, fetchImpl?: typeof fetch, timeoutMs?: number): Promise<CrmResult>;
198
+ //#endregion
199
+ //#region src/crm/jobber.d.ts
200
+ /**
201
+ * Write one record to Jobber. Never throws — a token-exchange failure, a
202
+ * network error, a top-level GraphQL `errors[]`, and a `userErrors[]` all
203
+ * come back as a `CrmResult` with `ok: false`.
204
+ */
205
+ export declare function submitToJobber(record: CrmRecord, config: JobberConfig, credentials: JobberCredentials, pageUri: string | undefined, fetchImpl?: typeof fetch, timeoutMs?: number): Promise<CrmResult>;
206
+ //#endregion
207
+ //#region src/crm/zoho-token.d.ts
208
+ export declare function zohoAccountsHost(dataCenter: string): string;
209
+ interface ZohoOAuth {
210
+ clientId: string;
211
+ clientSecret: string;
212
+ refreshToken: string;
213
+ }
214
+ /**
215
+ * Exchange a refresh token for a short-lived Bigin access token, reusing a
216
+ * cached one while it's still valid.
217
+ *
218
+ * `now` is a parameter so cache expiry is testable without a fake clock, the
219
+ * same convention `google/token.ts`'s `googleAccessToken` uses.
220
+ */
221
+ export declare function zohoAccessToken(oauth: ZohoOAuth, dataCenter: string, fetchImpl?: typeof fetch, now?: number): Promise<string>;
222
+ /** Test-only: clear the module-level cache between cases. */
223
+ export declare function clearZohoTokenCache(): void;
224
+ //#endregion
225
+ //#region src/crm/jobber-token.d.ts
226
+ /**
227
+ * Jobber OAuth2 refresh-token exchange.
228
+ *
229
+ * A plain `application/x-www-form-urlencoded` POST, same shape as Zoho's —
230
+ * see `zoho-token.ts` for why that keeps this Worker-safe (no `node:crypto`,
231
+ * unlike `google/token.ts`'s service-account JWT).
232
+ *
233
+ * One thing Zoho's grant never does: Jobber's Refresh Token Rotation is ON by
234
+ * default for a new app, and when it's on, every exchange returns a *new*
235
+ * refresh token and invalidates the old one. A Worker reading a static env
236
+ * var (`VITOPS_JOBBER_REFRESH_TOKEN`) cannot persist that new value, so this
237
+ * integration requires rotation turned OFF in the app's Developer Center
238
+ * settings — a legitimate setup requirement, since a per-client private app
239
+ * is never published to Jobber's Marketplace (where rotation ON is
240
+ * mandatory). `zohoAccessToken`'s Zoho equivalent has no such failure mode to
241
+ * detect, because Zoho's grant is long-lived by default.
242
+ *
243
+ * Cached per isolate, keyed on the refresh token, with an expiry margin —
244
+ * same rationale as `zoho-token.ts`.
245
+ */
246
+ interface JobberOAuth {
247
+ clientId: string;
248
+ clientSecret: string;
249
+ refreshToken: string;
250
+ }
251
+ /**
252
+ * Exchange a refresh token for a short-lived Jobber access token, reusing a
253
+ * cached one while it's still valid.
254
+ *
255
+ * Throws a specific, actionable error when Jobber's response carries a
256
+ * *different* refresh token than the one sent — the rotation case above.
257
+ * Without this check, rotation surfaces an hour later as an opaque 401 on a
258
+ * lead that is already lost; with it, the first submission after rotation
259
+ * turns on names the fix.
260
+ *
261
+ * `now` is a parameter so cache expiry is testable without a fake clock, the
262
+ * same convention `zoho-token.ts`'s `zohoAccessToken` uses.
263
+ */
264
+ export declare function jobberAccessToken(oauth: JobberOAuth, fetchImpl?: typeof fetch, now?: number): Promise<string>;
265
+ /** Test-only: clear the module-level cache between cases. */
266
+ export declare function clearJobberTokenCache(): void;
267
+ //#endregion
268
+ //#region src/crm/index.d.ts
269
+ export interface CrmDeps {
270
+ credentials?: CrmCredentials | undefined;
271
+ fetch?: typeof fetch | undefined;
272
+ timeoutMs?: number | undefined;
273
+ /**
274
+ * The visitor's page context, provider-neutral. HubSpot reads `hutk` (its
275
+ * own session cookie) to stitch the submission to its tracking; Jobber
276
+ * reads `pageUri` for the note body. Not every provider uses every field —
277
+ * an unused one is simply ignored, which is why this is one shape rather
278
+ * than a per-provider option the caller has to pick correctly.
279
+ */
280
+ pageContext?: HubSpotSubmitContext | undefined;
281
+ }
282
+ export declare function deliverToCrm(event: ConversionEvent, config: CrmConfig | undefined, deps?: CrmDeps): Promise<CrmResult | null>;
283
+ /** A one-line, human-readable delivery note — what `conversion.ts` folds into
284
+ * the notification email's "Delivery" section. */
285
+ export declare function describeCrmResult(result: CrmResult | null): string | null;
286
+ //#endregion
287
+ export type { BiginConfig, BiginCredentials, BiginDataCenter, BiginPipelineConfig, CrmConfig, CrmCredentials, CrmPlan, CrmProvider, CrmRecord, CrmResult, HubSpotConfig, HubSpotCredentials, HubSpotSubmitContext, JobberConfig, JobberCredentials, JobberOAuth };
288
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/crm/types.ts","../../src/crm/hubspot.ts","../../src/crm/map.ts","../../src/crm/plan.ts","../../src/crm/bigin.ts","../../src/crm/jobber.ts","../../src/crm/zoho-token.ts","../../src/crm/jobber-token.ts","../../src/crm/index.ts"],"mappings":";;;;;;;;;;;;;;;KAcY;;;;;qBAMC,iBAAiB,OAAO;;KAOzB;UAEK;EACf;EACA;;EAEA;;UAGe;EACf;EACA;EACA;;UAGe;EACf,YAAY;;EAEZ;EACA,WAAW;;UAGI;;;;;;;EAOf;;;;;;;;;;EAUA;EACA;;;;IAKM;;;UAKS;EACf,UAAU;EACV;EACA,UAAU;EACV,QAAQ;EACR,SAAS;;EAET,WAAW;;EAEX,iBAAiB;;;UAIF;EACf;;UAGe;EACf;EACA;EACA;;;;;;;;;UAUe;EACf;EACA;EACA;;KAGU;EACN;EAAqB,SAAS;;EAC9B;EAAmB,OAAO;;EAC1B;EAAoB,QAAQ;;;UAGjB;EACf;EACA,WAAW;;EAEX;;;;UAKe;EACf;EACA;EACA;EACA;EACA;EACA;;EAEA,OAAO;;;EAGP,aAAa;;;;;;;UAQE;EACf;EACA,UAAU;;EAEV;;EAEA;;EAEA;;;;UCjHe;EACf;EACA;EACA;;;;;;;;;;;;;wBAcoB,gBACpB,QAAQ,WACR,QAAQ,eACR,aAAa,oBACb,UAAS,sBACT,mBAAkB,OAClB,qBACC,QAAQ;;;;;;;;;;;;;wBCtCK,eAAe;EAC7B;EACA;EACA;EACA;;EACI;;EAAuB;;wBAgDb,cAAc,OAAO,iBAAiB,QAAQ,YAAY;;;wBC/C1D,QACd,QAAQ,uBACR,aAAa,6BACZ;;;wBCba,YAAY;;;;;wBAsBN,cACpB,QAAQ,WACR,QAAQ,aACR,aAAa,kBACb,mBAAkB,OAClB,qBACC,QAAQ;;;;;;;;wBCoJW,eACpB,QAAQ,WACR,QAAQ,cACR,aAAa,mBACb,6BACA,mBAAkB,OAClB,qBACC,QAAQ;;;wBCpLK,iBAAiB;UAehB;EACf;EACA;EACA;;;;;;;;;wBAUoB,gBACpB,OAAO,WACP,oBACA,mBAAkB,OAClB,eACC;;wBAkCa;;;;;;;;;;;;;;;;;;;;;;;UC3DC;EACf;EACA;EACA;;;;;;;;;;;;;;;wBAgBoB,kBACpB,OAAO,aACP,mBAAkB,OAClB,eACC;;wBA8Ca;;;iBCvEC;EACf,cAAc;EACd,eAAe;EACf;;;;;;;;EAQA,cAAc;;wBAGM,aACpB,OAAO,iBACP,QAAQ,uBACR,OAAM,UACL,QAAQ;;;wBAwDK,kBAAkB,QAAQ"}