@siloy/hubspot-provisioning 0.0.0-stage → 0.1.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 (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +203 -2
  3. package/dist/engine/src/blueprint/associations.d.ts +17 -0
  4. package/dist/engine/src/blueprint/associations.js +22 -0
  5. package/dist/engine/src/blueprint/field-types.d.ts +14 -0
  6. package/dist/engine/src/blueprint/field-types.js +22 -0
  7. package/dist/engine/src/blueprint/formula.d.ts +25 -0
  8. package/dist/engine/src/blueprint/formula.js +106 -0
  9. package/dist/engine/src/blueprint/groups.d.ts +16 -0
  10. package/dist/engine/src/blueprint/groups.js +30 -0
  11. package/dist/engine/src/blueprint/index.d.ts +10 -0
  12. package/dist/engine/src/blueprint/index.js +11 -0
  13. package/dist/engine/src/blueprint/mapping-targets.d.ts +10 -0
  14. package/dist/engine/src/blueprint/mapping-targets.js +5 -0
  15. package/dist/engine/src/blueprint/object-names.d.ts +11 -0
  16. package/dist/engine/src/blueprint/object-names.js +22 -0
  17. package/dist/engine/src/blueprint/pipelines.d.ts +40 -0
  18. package/dist/engine/src/blueprint/pipelines.js +99 -0
  19. package/dist/engine/src/blueprint/schema.d.ts +422 -0
  20. package/dist/engine/src/blueprint/schema.js +163 -0
  21. package/dist/engine/src/blueprint/serialize.d.ts +22 -0
  22. package/dist/engine/src/blueprint/serialize.js +70 -0
  23. package/dist/engine/src/blueprint/validate.d.ts +35 -0
  24. package/dist/engine/src/blueprint/validate.js +385 -0
  25. package/dist/engine/src/errors.d.ts +40 -0
  26. package/dist/engine/src/errors.js +73 -0
  27. package/dist/provisioning/src/api/client.d.ts +36 -0
  28. package/dist/provisioning/src/api/client.js +186 -0
  29. package/dist/provisioning/src/api/commit.d.ts +13 -0
  30. package/dist/provisioning/src/api/commit.js +12 -0
  31. package/dist/provisioning/src/api/types.d.ts +98 -0
  32. package/dist/provisioning/src/api/types.js +5 -0
  33. package/dist/provisioning/src/cli/commands.d.ts +16 -0
  34. package/dist/provisioning/src/cli/commands.js +373 -0
  35. package/dist/provisioning/src/cli/hints.d.ts +4 -0
  36. package/dist/provisioning/src/cli/hints.js +21 -0
  37. package/dist/provisioning/src/cli/main.d.ts +2 -0
  38. package/dist/provisioning/src/cli/main.js +24 -0
  39. package/dist/provisioning/src/cli/render.d.ts +25 -0
  40. package/dist/provisioning/src/cli/render.js +149 -0
  41. package/dist/provisioning/src/index.d.ts +6 -0
  42. package/dist/provisioning/src/index.js +5 -0
  43. package/dist/provisioning/src/limits.d.ts +2 -0
  44. package/dist/provisioning/src/limits.js +2 -0
  45. package/dist/provisioning/src/targets.d.ts +16 -0
  46. package/dist/provisioning/src/targets.js +27 -0
  47. package/dist/provisioning/src/validate.d.ts +10 -0
  48. package/dist/provisioning/src/validate.js +15 -0
  49. package/dist/provisioning/src/version.d.ts +2 -0
  50. package/dist/provisioning/src/version.js +17 -0
  51. package/package.json +38 -4
  52. package/schema/blueprint.schema.json +420 -0
@@ -0,0 +1,99 @@
1
+ import { isStandardObject } from './schema.js';
2
+ /** Pipelines exist on deals, tickets and custom objects only (P1). */
3
+ export function canHavePipelines(objectType) {
4
+ return objectType === 'deals' || objectType === 'tickets' || !isStandardObject(objectType);
5
+ }
6
+ /** Each object kind keeps its stage semantics under a different metadata key (P2). */
7
+ export function metadataKeyFor(objectType) {
8
+ if (objectType === 'deals')
9
+ return 'probability';
10
+ if (objectType === 'tickets')
11
+ return 'ticketState';
12
+ return 'state';
13
+ }
14
+ /** The pipeline HubSpot ships with a portal; adopted with origin: hubspot, never created. */
15
+ export const DEFAULT_PIPELINE_IDS = { deals: 'default', tickets: '0' };
16
+ /** The pipeline every new portal has on deals and tickets, measured on portals A and B (docs/hubspot-quirks.md, Pipelines). */
17
+ export const STOCK_PIPELINES = {
18
+ deals: {
19
+ id: 'default',
20
+ label: 'Sales Pipeline',
21
+ stages: [
22
+ { id: 'appointmentscheduled', label: 'Appointment Scheduled', metadata: { probability: '0.2' } },
23
+ { id: 'qualifiedtobuy', label: 'Qualified To Buy', metadata: { probability: '0.4' } },
24
+ { id: 'presentationscheduled', label: 'Presentation Scheduled', metadata: { probability: '0.6' } },
25
+ { id: 'decisionmakerboughtin', label: 'Decision Maker Bought-In', metadata: { probability: '0.8' } },
26
+ { id: 'contractsent', label: 'Contract Sent', metadata: { probability: '0.9' } },
27
+ { id: 'closedwon', label: 'Closed Won', metadata: { probability: '1.0' } },
28
+ { id: 'closedlost', label: 'Closed Lost', metadata: { probability: '0.0' } },
29
+ ],
30
+ },
31
+ tickets: {
32
+ id: '0',
33
+ label: 'Support Pipeline',
34
+ stages: [
35
+ { id: '1', label: 'New', metadata: { ticketState: 'OPEN' } },
36
+ { id: '2', label: 'Waiting on contact', metadata: { ticketState: 'OPEN' } },
37
+ { id: '3', label: 'Waiting on us', metadata: { ticketState: 'OPEN' } },
38
+ { id: '4', label: 'Closed', metadata: { ticketState: 'CLOSED' } },
39
+ ],
40
+ },
41
+ };
42
+ /**
43
+ * The stock pipeline as a blueprint pipeline with origin: hubspot, for the editor's "Adopt HubSpot's default
44
+ * pipeline". Keys are slugged from the labels, as ingest does without state; the planner matches the portal's
45
+ * stages by label.
46
+ */
47
+ export function adoptablePipeline(objectType) {
48
+ const stock = STOCK_PIPELINES[objectType];
49
+ return {
50
+ key: pipelineKeySlug(stock.label, 'p'),
51
+ origin: 'hubspot',
52
+ label: stock.label,
53
+ stages: stock.stages.map((stage) => ({ key: pipelineKeySlug(stage.label, 's'), label: stage.label, metadata: { ...stage.metadata } })),
54
+ };
55
+ }
56
+ /** The record property holding the stage id. */
57
+ export function stagePropertyFor(objectType) {
58
+ return objectType === 'deals' ? 'dealstage' : 'hs_pipeline_stage';
59
+ }
60
+ /** HubSpot generates one set of these per stage; they are not ours to manage. */
61
+ export const STAGE_PROPERTY_PATTERN = /^hs_v2_(date_entered|date_exited|cumulative_time_in|latest_time_in)_(.+)$/;
62
+ export function isStageGeneratedProperty(name) {
63
+ const match = STAGE_PROPERTY_PATTERN.exec(name);
64
+ return match !== null && match[2] !== 'current_stage';
65
+ }
66
+ /** Properties that pipelines and stages own, so the property engine must leave them alone. */
67
+ export function isPipelineOwnedProperty(objectType, name) {
68
+ return isOptionsOwnedByPipelines(objectType, name) || isStageGeneratedProperty(name);
69
+ }
70
+ /** The four properties whose options are the pipelines and stages themselves. */
71
+ export function isOptionsOwnedByPipelines(objectType, name) {
72
+ return name === 'hs_pipeline' || name === 'hs_pipeline_stage' || (objectType === 'deals' && (name === 'dealstage' || name === 'pipeline'));
73
+ }
74
+ /** A readable identifier from a label: lower case letters, digits and underscores, starting with a letter. */
75
+ export function identifierSlug(label, prefix, fallback) {
76
+ const slug = label
77
+ .normalize('NFKD')
78
+ .replace(/[\u0300-\u036f]/g, '')
79
+ .toLowerCase()
80
+ .replace(/[^a-z0-9]+/g, '_')
81
+ .replace(/^_+|_+$/g, '');
82
+ if (slug === '')
83
+ return fallback;
84
+ return /^[a-z]/.test(slug) ? slug : `${prefix}_${slug}`;
85
+ }
86
+ /** A readable, identifier-safe key from a label. */
87
+ export function pipelineKeySlug(label, prefix) {
88
+ return identifierSlug(label, prefix, prefix === 'p' ? 'p_pipeline' : 's_stage');
89
+ }
90
+ /** isClosed is derived by HubSpot, so we infer it from the metadata that drives it (P2). */
91
+ export function isClosedStage(objectType, metadata) {
92
+ if (objectType === 'tickets')
93
+ return metadata.ticketState === 'CLOSED';
94
+ if (objectType === 'deals') {
95
+ const probability = Number(metadata.probability);
96
+ return metadata.probability !== undefined && (probability === 0 || probability === 1);
97
+ }
98
+ return metadata.state === 'CLOSED';
99
+ }
@@ -0,0 +1,422 @@
1
+ import { z } from 'zod';
2
+ export declare const BLUEPRINT_FORMAT = "hubspot-blueprint/v1";
3
+ /** Standard objects the engine manages, with HubSpot's fixed object type ids. */
4
+ export declare const STANDARD_OBJECTS: {
5
+ readonly contacts: '0-1';
6
+ readonly companies: '0-2';
7
+ readonly deals: '0-3';
8
+ readonly tickets: '0-5';
9
+ readonly products: '0-7';
10
+ readonly line_items: '0-8';
11
+ };
12
+ export type StandardObject = keyof typeof STANDARD_OBJECTS;
13
+ export declare function isStandardObject(name: string): name is StandardObject;
14
+ export declare const PROPERTY_TYPES: readonly ['string', 'number', 'date', 'datetime', 'enumeration', 'bool', 'phone_number'];
15
+ /** The only calculated kind HubSpot can read and create through the API (docs/hubspot-quirks.md, Q16). */
16
+ export declare const EQUATION_FIELD_TYPE = "calculation_equation";
17
+ /** Result types HubSpot accepts for an equation; text and dropdown equations are rejected (400). */
18
+ export declare const EQUATION_TYPES: ReadonlyArray<string>;
19
+ /** HubSpot's highest per-record association limit. */
20
+ export declare const ASSOCIATION_LIMIT_MAX = 10000;
21
+ export declare function isEquation(property: {
22
+ fieldType: string;
23
+ }): boolean;
24
+ /**
25
+ * A property group's internal name: HubSpot keeps the label's characters ('identity_&_sync_keys'), and a group with
26
+ * '/' cannot be read or patched after creation (docs/hubspot-quirks.md, Q18). HubSpot also rejects uppercase; that
27
+ * is a semantic check in validate.ts, which stored content may skip because the old identifier rule allowed it.
28
+ */
29
+ export declare const GROUP_NAME_MAX = 100;
30
+ export declare const GROUP_NAME_RULE = "must be 1 to 100 characters, without '/' or control characters";
31
+ export declare function isGroupName(name: string): boolean;
32
+ export declare const GROUP_NAME_LOWERCASE = "HubSpot only accepts lowercase group names";
33
+ /** False for a name HubSpot rejects for its uppercase; such a group can only come from an old stored version. */
34
+ export declare function isLowercaseGroupName(name: string): boolean;
35
+ export declare const OptionSchema: z.ZodObject<{
36
+ value: z.ZodString;
37
+ label: z.ZodString;
38
+ displayOrder: z.ZodOptional<z.ZodNumber>;
39
+ hidden: z.ZodDefault<z.ZodBoolean>;
40
+ description: z.ZodOptional<z.ZodString>;
41
+ }, z.core.$strict>, PropertySchema: z.ZodObject<{
42
+ name: z.ZodString;
43
+ origin: z.ZodDefault<z.ZodEnum<{
44
+ custom: "custom";
45
+ hubspot: "hubspot";
46
+ }>>;
47
+ label: z.ZodString;
48
+ type: z.ZodEnum<{
49
+ bool: "bool";
50
+ date: "date";
51
+ datetime: "datetime";
52
+ enumeration: "enumeration";
53
+ number: "number";
54
+ phone_number: "phone_number";
55
+ string: "string";
56
+ }>;
57
+ fieldType: z.ZodString;
58
+ groupName: z.ZodString;
59
+ description: z.ZodOptional<z.ZodString>;
60
+ displayOrder: z.ZodOptional<z.ZodNumber>;
61
+ hidden: z.ZodOptional<z.ZodBoolean>;
62
+ formField: z.ZodOptional<z.ZodBoolean>;
63
+ hasUniqueValue: z.ZodDefault<z.ZodBoolean>;
64
+ referencedObjectType: z.ZodOptional<z.ZodString>;
65
+ externalOptions: z.ZodOptional<z.ZodBoolean>;
66
+ calculationFormula: z.ZodOptional<z.ZodString>;
67
+ numberDisplayHint: z.ZodOptional<z.ZodString>;
68
+ showCurrencySymbol: z.ZodOptional<z.ZodBoolean>;
69
+ options: z.ZodDefault<z.ZodArray<z.ZodObject<{
70
+ value: z.ZodString;
71
+ label: z.ZodString;
72
+ displayOrder: z.ZodOptional<z.ZodNumber>;
73
+ hidden: z.ZodDefault<z.ZodBoolean>;
74
+ description: z.ZodOptional<z.ZodString>;
75
+ }, z.core.$strict>>>;
76
+ }, z.core.$strict>, PropertyGroupSchema: z.ZodObject<{
77
+ name: z.ZodString;
78
+ label: z.ZodString;
79
+ displayOrder: z.ZodOptional<z.ZodNumber>;
80
+ }, z.core.$strict>, CustomObjectSchema: z.ZodObject<{
81
+ labels: z.ZodObject<{
82
+ singular: z.ZodString;
83
+ plural: z.ZodString;
84
+ }, z.core.$strict>;
85
+ description: z.ZodOptional<z.ZodString>;
86
+ primaryDisplayProperty: z.ZodString;
87
+ secondaryDisplayProperties: z.ZodDefault<z.ZodArray<z.ZodString>>;
88
+ requiredProperties: z.ZodDefault<z.ZodArray<z.ZodString>>;
89
+ searchableProperties: z.ZodDefault<z.ZodArray<z.ZodString>>;
90
+ }, z.core.$strict>, StageMetadataSchema: z.ZodObject<{
91
+ probability: z.ZodOptional<z.ZodString>;
92
+ ticketState: z.ZodOptional<z.ZodString>;
93
+ state: z.ZodOptional<z.ZodString>;
94
+ }, z.core.$strict>, StageSchema: z.ZodObject<{
95
+ key: z.ZodString;
96
+ label: z.ZodString;
97
+ metadata: z.ZodObject<{
98
+ probability: z.ZodOptional<z.ZodString>;
99
+ ticketState: z.ZodOptional<z.ZodString>;
100
+ state: z.ZodOptional<z.ZodString>;
101
+ }, z.core.$strict>;
102
+ }, z.core.$strict>, PipelineSchema: z.ZodObject<{
103
+ key: z.ZodString;
104
+ origin: z.ZodDefault<z.ZodEnum<{
105
+ custom: "custom";
106
+ hubspot: "hubspot";
107
+ }>>;
108
+ label: z.ZodString;
109
+ displayOrder: z.ZodOptional<z.ZodNumber>;
110
+ stages: z.ZodArray<z.ZodObject<{
111
+ key: z.ZodString;
112
+ label: z.ZodString;
113
+ metadata: z.ZodObject<{
114
+ probability: z.ZodOptional<z.ZodString>;
115
+ ticketState: z.ZodOptional<z.ZodString>;
116
+ state: z.ZodOptional<z.ZodString>;
117
+ }, z.core.$strict>;
118
+ }, z.core.$strict>>;
119
+ }, z.core.$strict>, ObjectSchema: z.ZodObject<{
120
+ objectType: z.ZodString;
121
+ schema: z.ZodOptional<z.ZodObject<{
122
+ labels: z.ZodObject<{
123
+ singular: z.ZodString;
124
+ plural: z.ZodString;
125
+ }, z.core.$strict>;
126
+ description: z.ZodOptional<z.ZodString>;
127
+ primaryDisplayProperty: z.ZodString;
128
+ secondaryDisplayProperties: z.ZodDefault<z.ZodArray<z.ZodString>>;
129
+ requiredProperties: z.ZodDefault<z.ZodArray<z.ZodString>>;
130
+ searchableProperties: z.ZodDefault<z.ZodArray<z.ZodString>>;
131
+ }, z.core.$strict>>;
132
+ labels: z.ZodOptional<z.ZodObject<{
133
+ singular: z.ZodString;
134
+ plural: z.ZodString;
135
+ }, z.core.$strict>>;
136
+ propertyGroups: z.ZodDefault<z.ZodArray<z.ZodObject<{
137
+ name: z.ZodString;
138
+ label: z.ZodString;
139
+ displayOrder: z.ZodOptional<z.ZodNumber>;
140
+ }, z.core.$strict>>>;
141
+ properties: z.ZodDefault<z.ZodArray<z.ZodObject<{
142
+ name: z.ZodString;
143
+ origin: z.ZodDefault<z.ZodEnum<{
144
+ custom: "custom";
145
+ hubspot: "hubspot";
146
+ }>>;
147
+ label: z.ZodString;
148
+ type: z.ZodEnum<{
149
+ bool: "bool";
150
+ date: "date";
151
+ datetime: "datetime";
152
+ enumeration: "enumeration";
153
+ number: "number";
154
+ phone_number: "phone_number";
155
+ string: "string";
156
+ }>;
157
+ fieldType: z.ZodString;
158
+ groupName: z.ZodString;
159
+ description: z.ZodOptional<z.ZodString>;
160
+ displayOrder: z.ZodOptional<z.ZodNumber>;
161
+ hidden: z.ZodOptional<z.ZodBoolean>;
162
+ formField: z.ZodOptional<z.ZodBoolean>;
163
+ hasUniqueValue: z.ZodDefault<z.ZodBoolean>;
164
+ referencedObjectType: z.ZodOptional<z.ZodString>;
165
+ externalOptions: z.ZodOptional<z.ZodBoolean>;
166
+ calculationFormula: z.ZodOptional<z.ZodString>;
167
+ numberDisplayHint: z.ZodOptional<z.ZodString>;
168
+ showCurrencySymbol: z.ZodOptional<z.ZodBoolean>;
169
+ options: z.ZodDefault<z.ZodArray<z.ZodObject<{
170
+ value: z.ZodString;
171
+ label: z.ZodString;
172
+ displayOrder: z.ZodOptional<z.ZodNumber>;
173
+ hidden: z.ZodDefault<z.ZodBoolean>;
174
+ description: z.ZodOptional<z.ZodString>;
175
+ }, z.core.$strict>>>;
176
+ }, z.core.$strict>>>;
177
+ pipelines: z.ZodDefault<z.ZodArray<z.ZodObject<{
178
+ key: z.ZodString;
179
+ origin: z.ZodDefault<z.ZodEnum<{
180
+ custom: "custom";
181
+ hubspot: "hubspot";
182
+ }>>;
183
+ label: z.ZodString;
184
+ displayOrder: z.ZodOptional<z.ZodNumber>;
185
+ stages: z.ZodArray<z.ZodObject<{
186
+ key: z.ZodString;
187
+ label: z.ZodString;
188
+ metadata: z.ZodObject<{
189
+ probability: z.ZodOptional<z.ZodString>;
190
+ ticketState: z.ZodOptional<z.ZodString>;
191
+ state: z.ZodOptional<z.ZodString>;
192
+ }, z.core.$strict>;
193
+ }, z.core.$strict>>;
194
+ }, z.core.$strict>>>;
195
+ }, z.core.$strict>, AssociationLabelSchema: z.ZodObject<{
196
+ key: z.ZodString;
197
+ label: z.ZodString;
198
+ inverseLabel: z.ZodOptional<z.ZodString>;
199
+ limit: z.ZodOptional<z.ZodNumber>;
200
+ inverseLimit: z.ZodOptional<z.ZodNumber>;
201
+ }, z.core.$strict>, AssociationSchema: z.ZodObject<{
202
+ from: z.ZodString;
203
+ to: z.ZodString;
204
+ limit: z.ZodOptional<z.ZodNumber>;
205
+ inverseLimit: z.ZodOptional<z.ZodNumber>;
206
+ labels: z.ZodDefault<z.ZodArray<z.ZodObject<{
207
+ key: z.ZodString;
208
+ label: z.ZodString;
209
+ inverseLabel: z.ZodOptional<z.ZodString>;
210
+ limit: z.ZodOptional<z.ZodNumber>;
211
+ inverseLimit: z.ZodOptional<z.ZodNumber>;
212
+ }, z.core.$strict>>>;
213
+ }, z.core.$strict>, BlueprintSchema: z.ZodObject<{
214
+ format: z.ZodLiteral<"hubspot-blueprint/v1">;
215
+ source: z.ZodOptional<z.ZodObject<{
216
+ portalId: z.ZodNumber;
217
+ ingestedAt: z.ZodString;
218
+ }, z.core.$strict>>;
219
+ objects: z.ZodArray<z.ZodObject<{
220
+ objectType: z.ZodString;
221
+ schema: z.ZodOptional<z.ZodObject<{
222
+ labels: z.ZodObject<{
223
+ singular: z.ZodString;
224
+ plural: z.ZodString;
225
+ }, z.core.$strict>;
226
+ description: z.ZodOptional<z.ZodString>;
227
+ primaryDisplayProperty: z.ZodString;
228
+ secondaryDisplayProperties: z.ZodDefault<z.ZodArray<z.ZodString>>;
229
+ requiredProperties: z.ZodDefault<z.ZodArray<z.ZodString>>;
230
+ searchableProperties: z.ZodDefault<z.ZodArray<z.ZodString>>;
231
+ }, z.core.$strict>>;
232
+ labels: z.ZodOptional<z.ZodObject<{
233
+ singular: z.ZodString;
234
+ plural: z.ZodString;
235
+ }, z.core.$strict>>;
236
+ propertyGroups: z.ZodDefault<z.ZodArray<z.ZodObject<{
237
+ name: z.ZodString;
238
+ label: z.ZodString;
239
+ displayOrder: z.ZodOptional<z.ZodNumber>;
240
+ }, z.core.$strict>>>;
241
+ properties: z.ZodDefault<z.ZodArray<z.ZodObject<{
242
+ name: z.ZodString;
243
+ origin: z.ZodDefault<z.ZodEnum<{
244
+ custom: "custom";
245
+ hubspot: "hubspot";
246
+ }>>;
247
+ label: z.ZodString;
248
+ type: z.ZodEnum<{
249
+ bool: "bool";
250
+ date: "date";
251
+ datetime: "datetime";
252
+ enumeration: "enumeration";
253
+ number: "number";
254
+ phone_number: "phone_number";
255
+ string: "string";
256
+ }>;
257
+ fieldType: z.ZodString;
258
+ groupName: z.ZodString;
259
+ description: z.ZodOptional<z.ZodString>;
260
+ displayOrder: z.ZodOptional<z.ZodNumber>;
261
+ hidden: z.ZodOptional<z.ZodBoolean>;
262
+ formField: z.ZodOptional<z.ZodBoolean>;
263
+ hasUniqueValue: z.ZodDefault<z.ZodBoolean>;
264
+ referencedObjectType: z.ZodOptional<z.ZodString>;
265
+ externalOptions: z.ZodOptional<z.ZodBoolean>;
266
+ calculationFormula: z.ZodOptional<z.ZodString>;
267
+ numberDisplayHint: z.ZodOptional<z.ZodString>;
268
+ showCurrencySymbol: z.ZodOptional<z.ZodBoolean>;
269
+ options: z.ZodDefault<z.ZodArray<z.ZodObject<{
270
+ value: z.ZodString;
271
+ label: z.ZodString;
272
+ displayOrder: z.ZodOptional<z.ZodNumber>;
273
+ hidden: z.ZodDefault<z.ZodBoolean>;
274
+ description: z.ZodOptional<z.ZodString>;
275
+ }, z.core.$strict>>>;
276
+ }, z.core.$strict>>>;
277
+ pipelines: z.ZodDefault<z.ZodArray<z.ZodObject<{
278
+ key: z.ZodString;
279
+ origin: z.ZodDefault<z.ZodEnum<{
280
+ custom: "custom";
281
+ hubspot: "hubspot";
282
+ }>>;
283
+ label: z.ZodString;
284
+ displayOrder: z.ZodOptional<z.ZodNumber>;
285
+ stages: z.ZodArray<z.ZodObject<{
286
+ key: z.ZodString;
287
+ label: z.ZodString;
288
+ metadata: z.ZodObject<{
289
+ probability: z.ZodOptional<z.ZodString>;
290
+ ticketState: z.ZodOptional<z.ZodString>;
291
+ state: z.ZodOptional<z.ZodString>;
292
+ }, z.core.$strict>;
293
+ }, z.core.$strict>>;
294
+ }, z.core.$strict>>>;
295
+ }, z.core.$strict>>;
296
+ associations: z.ZodDefault<z.ZodArray<z.ZodObject<{
297
+ from: z.ZodString;
298
+ to: z.ZodString;
299
+ limit: z.ZodOptional<z.ZodNumber>;
300
+ inverseLimit: z.ZodOptional<z.ZodNumber>;
301
+ labels: z.ZodDefault<z.ZodArray<z.ZodObject<{
302
+ key: z.ZodString;
303
+ label: z.ZodString;
304
+ inverseLabel: z.ZodOptional<z.ZodString>;
305
+ limit: z.ZodOptional<z.ZodNumber>;
306
+ inverseLimit: z.ZodOptional<z.ZodNumber>;
307
+ }, z.core.$strict>>>;
308
+ }, z.core.$strict>>>;
309
+ }, z.core.$strict>;
310
+ /** What the editor's forms can render (spec #27 §4.3). Never used to accept a version. */
311
+ export declare const DraftBlueprintSchema: z.ZodObject<{
312
+ format: z.ZodLiteral<"hubspot-blueprint/v1">;
313
+ source: z.ZodOptional<z.ZodObject<{
314
+ portalId: z.ZodNumber;
315
+ ingestedAt: z.ZodString;
316
+ }, z.core.$strict>>;
317
+ objects: z.ZodArray<z.ZodObject<{
318
+ objectType: z.ZodString;
319
+ schema: z.ZodOptional<z.ZodObject<{
320
+ labels: z.ZodObject<{
321
+ singular: z.ZodString;
322
+ plural: z.ZodString;
323
+ }, z.core.$strict>;
324
+ description: z.ZodOptional<z.ZodString>;
325
+ primaryDisplayProperty: z.ZodString;
326
+ secondaryDisplayProperties: z.ZodDefault<z.ZodArray<z.ZodString>>;
327
+ requiredProperties: z.ZodDefault<z.ZodArray<z.ZodString>>;
328
+ searchableProperties: z.ZodDefault<z.ZodArray<z.ZodString>>;
329
+ }, z.core.$strict>>;
330
+ labels: z.ZodOptional<z.ZodObject<{
331
+ singular: z.ZodString;
332
+ plural: z.ZodString;
333
+ }, z.core.$strict>>;
334
+ propertyGroups: z.ZodDefault<z.ZodArray<z.ZodObject<{
335
+ name: z.ZodString;
336
+ label: z.ZodString;
337
+ displayOrder: z.ZodOptional<z.ZodNumber>;
338
+ }, z.core.$strict>>>;
339
+ properties: z.ZodDefault<z.ZodArray<z.ZodObject<{
340
+ name: z.ZodString;
341
+ origin: z.ZodDefault<z.ZodEnum<{
342
+ custom: "custom";
343
+ hubspot: "hubspot";
344
+ }>>;
345
+ label: z.ZodString;
346
+ type: z.ZodEnum<{
347
+ bool: "bool";
348
+ date: "date";
349
+ datetime: "datetime";
350
+ enumeration: "enumeration";
351
+ number: "number";
352
+ phone_number: "phone_number";
353
+ string: "string";
354
+ }>;
355
+ fieldType: z.ZodString;
356
+ groupName: z.ZodString;
357
+ description: z.ZodOptional<z.ZodString>;
358
+ displayOrder: z.ZodOptional<z.ZodNumber>;
359
+ hidden: z.ZodOptional<z.ZodBoolean>;
360
+ formField: z.ZodOptional<z.ZodBoolean>;
361
+ hasUniqueValue: z.ZodDefault<z.ZodBoolean>;
362
+ referencedObjectType: z.ZodOptional<z.ZodString>;
363
+ externalOptions: z.ZodOptional<z.ZodBoolean>;
364
+ calculationFormula: z.ZodOptional<z.ZodString>;
365
+ numberDisplayHint: z.ZodOptional<z.ZodString>;
366
+ showCurrencySymbol: z.ZodOptional<z.ZodBoolean>;
367
+ options: z.ZodDefault<z.ZodArray<z.ZodObject<{
368
+ value: z.ZodString;
369
+ label: z.ZodString;
370
+ displayOrder: z.ZodOptional<z.ZodNumber>;
371
+ hidden: z.ZodDefault<z.ZodBoolean>;
372
+ description: z.ZodOptional<z.ZodString>;
373
+ }, z.core.$strict>>>;
374
+ }, z.core.$strict>>>;
375
+ pipelines: z.ZodDefault<z.ZodArray<z.ZodObject<{
376
+ key: z.ZodString;
377
+ origin: z.ZodDefault<z.ZodEnum<{
378
+ custom: "custom";
379
+ hubspot: "hubspot";
380
+ }>>;
381
+ label: z.ZodString;
382
+ displayOrder: z.ZodOptional<z.ZodNumber>;
383
+ stages: z.ZodArray<z.ZodObject<{
384
+ key: z.ZodString;
385
+ label: z.ZodString;
386
+ metadata: z.ZodObject<{
387
+ probability: z.ZodOptional<z.ZodString>;
388
+ ticketState: z.ZodOptional<z.ZodString>;
389
+ state: z.ZodOptional<z.ZodString>;
390
+ }, z.core.$strict>;
391
+ }, z.core.$strict>>;
392
+ }, z.core.$strict>>>;
393
+ }, z.core.$strict>>;
394
+ associations: z.ZodDefault<z.ZodArray<z.ZodObject<{
395
+ from: z.ZodString;
396
+ to: z.ZodString;
397
+ limit: z.ZodOptional<z.ZodNumber>;
398
+ inverseLimit: z.ZodOptional<z.ZodNumber>;
399
+ labels: z.ZodDefault<z.ZodArray<z.ZodObject<{
400
+ key: z.ZodString;
401
+ label: z.ZodString;
402
+ inverseLabel: z.ZodOptional<z.ZodString>;
403
+ limit: z.ZodOptional<z.ZodNumber>;
404
+ inverseLimit: z.ZodOptional<z.ZodNumber>;
405
+ }, z.core.$strict>>>;
406
+ }, z.core.$strict>>>;
407
+ }, z.core.$strict>;
408
+ export type Blueprint = z.output<typeof BlueprintSchema>;
409
+ /** An editor draft: the shape of a Blueprint, not yet valid. */
410
+ export type DraftBlueprint = z.output<typeof DraftBlueprintSchema>;
411
+ export type BlueprintObject = Blueprint['objects'][number];
412
+ export type CustomObjectDefinition = NonNullable<BlueprintObject['schema']>;
413
+ export type PropertyGroup = BlueprintObject['propertyGroups'][number];
414
+ export type Property = BlueprintObject['properties'][number];
415
+ export type BlueprintOption = Property['options'][number];
416
+ /** An object pair, with its labels nested. */
417
+ export type Association = Blueprint['associations'][number];
418
+ export type AssociationLabel = Association['labels'][number];
419
+ export type PropertyType = (typeof PROPERTY_TYPES)[number];
420
+ export type Pipeline = BlueprintObject['pipelines'][number];
421
+ export type Stage = Pipeline['stages'][number];
422
+ export type StageMetadata = Stage['metadata'];
@@ -0,0 +1,163 @@
1
+ import { z } from 'zod';
2
+ export const BLUEPRINT_FORMAT = 'hubspot-blueprint/v1';
3
+ /** Standard objects the engine manages, with HubSpot's fixed object type ids. */
4
+ export const STANDARD_OBJECTS = {
5
+ contacts: '0-1',
6
+ companies: '0-2',
7
+ deals: '0-3',
8
+ tickets: '0-5',
9
+ products: '0-7',
10
+ line_items: '0-8',
11
+ };
12
+ export function isStandardObject(name) {
13
+ return Object.hasOwn(STANDARD_OBJECTS, name);
14
+ }
15
+ export const PROPERTY_TYPES = ['string', 'number', 'date', 'datetime', 'enumeration', 'bool', 'phone_number'];
16
+ /** The only calculated kind HubSpot can read and create through the API (docs/hubspot-quirks.md, Q16). */
17
+ export const EQUATION_FIELD_TYPE = 'calculation_equation';
18
+ /** Result types HubSpot accepts for an equation; text and dropdown equations are rejected (400). */
19
+ export const EQUATION_TYPES = ['number', 'bool', 'date', 'datetime'];
20
+ /** HubSpot's highest per-record association limit. */
21
+ export const ASSOCIATION_LIMIT_MAX = 10000;
22
+ export function isEquation(property) {
23
+ return property.fieldType === EQUATION_FIELD_TYPE;
24
+ }
25
+ const strictIdentifier = z
26
+ .string()
27
+ .regex(/^[A-Za-z][A-Za-z0-9_]*$/, 'must start with a letter and contain only letters, digits and underscores');
28
+ /**
29
+ * A property group's internal name: HubSpot keeps the label's characters ('identity_&_sync_keys'), and a group with
30
+ * '/' cannot be read or patched after creation (docs/hubspot-quirks.md, Q18). HubSpot also rejects uppercase; that
31
+ * is a semantic check in validate.ts, which stored content may skip because the old identifier rule allowed it.
32
+ */
33
+ export const GROUP_NAME_MAX = 100;
34
+ const GROUP_NAME = new RegExp(`^[^/\\u0000-\\u001f\\u007f-\\u009f]{1,${GROUP_NAME_MAX}}$`);
35
+ export const GROUP_NAME_RULE = `must be 1 to ${GROUP_NAME_MAX} characters, without '/' or control characters`;
36
+ export function isGroupName(name) {
37
+ return GROUP_NAME.test(name);
38
+ }
39
+ export const GROUP_NAME_LOWERCASE = 'HubSpot only accepts lowercase group names';
40
+ /** False for a name HubSpot rejects for its uppercase; such a group can only come from an old stored version. */
41
+ export function isLowercaseGroupName(name) {
42
+ return name === name.toLowerCase();
43
+ }
44
+ const strictGroupName = z.string().regex(GROUP_NAME, GROUP_NAME_RULE);
45
+ const strictText = z.string().min(1);
46
+ const LIMIT_MESSAGE = `a limit is a whole number from 1 to ${ASSOCIATION_LIMIT_MAX}`;
47
+ const strictLimit = z.number().int(LIMIT_MESSAGE).min(1, LIMIT_MESSAGE).max(ASSOCIATION_LIMIT_MAX, LIMIT_MESSAGE);
48
+ /**
49
+ * Builds the blueprint schemas. `draft`: the same structure with plain strings and no minimum stage count,
50
+ * so an editor draft with an empty label or a pipeline without stages still parses (spec #27 §4.3).
51
+ */
52
+ function buildSchemas(draft) {
53
+ const identifier = draft ? z.string() : strictIdentifier;
54
+ const groupName = draft ? z.string() : strictGroupName;
55
+ const text = draft ? z.string() : strictText;
56
+ const limit = draft ? z.number() : strictLimit;
57
+ const OptionSchema = z.strictObject({
58
+ value: text,
59
+ label: text,
60
+ displayOrder: z.number().int().optional(),
61
+ hidden: z.boolean().default(false),
62
+ description: z.string().optional(),
63
+ });
64
+ const PropertySchema = z.strictObject({
65
+ name: identifier,
66
+ /** custom: created by us. hubspot: a HubSpot-defined property we take over (patch only, never create). */
67
+ origin: z.enum(['custom', 'hubspot']).default('custom'),
68
+ label: text,
69
+ type: z.enum(PROPERTY_TYPES),
70
+ fieldType: text,
71
+ groupName,
72
+ description: z.string().optional(),
73
+ displayOrder: z.number().int().optional(),
74
+ hidden: z.boolean().optional(),
75
+ formField: z.boolean().optional(),
76
+ hasUniqueValue: z.boolean().default(false),
77
+ referencedObjectType: z.string().optional(),
78
+ externalOptions: z.boolean().optional(),
79
+ /** calculation_equation only (and then required). Refers to properties of the same object by name. */
80
+ calculationFormula: text.optional(),
81
+ /** number only. Omitted = unmanaged. */
82
+ numberDisplayHint: text.optional(),
83
+ /** number only. Omitted = unmanaged. */
84
+ showCurrencySymbol: z.boolean().optional(),
85
+ options: z.array(OptionSchema).default([]),
86
+ });
87
+ const PropertyGroupSchema = z.strictObject({
88
+ name: groupName,
89
+ label: text,
90
+ displayOrder: z.number().int().optional(),
91
+ });
92
+ const CustomObjectSchema = z.strictObject({
93
+ labels: z.strictObject({ singular: text, plural: text }),
94
+ description: z.string().optional(),
95
+ primaryDisplayProperty: identifier,
96
+ /** Ordered: renders left-to-right on a record. */
97
+ secondaryDisplayProperties: z.array(identifier).default([]),
98
+ /** Sets: order is meaningless. */
99
+ requiredProperties: z.array(identifier).default([]),
100
+ searchableProperties: z.array(identifier).default([]),
101
+ });
102
+ /** Which key is meaningful depends on the object kind (see pipelines.ts); validate.ts enforces that. */
103
+ const StageMetadataSchema = z.strictObject({
104
+ probability: text.optional(),
105
+ ticketState: text.optional(),
106
+ state: text.optional(),
107
+ });
108
+ /** Stage order is the array order; there is deliberately no displayOrder. */
109
+ const StageSchema = z.strictObject({
110
+ key: identifier,
111
+ label: text,
112
+ metadata: StageMetadataSchema,
113
+ });
114
+ const PipelineSchema = z.strictObject({
115
+ key: identifier,
116
+ /** custom: created by us. hubspot: the portal's default pipeline, adopted (deals and tickets only). */
117
+ origin: z.enum(['custom', 'hubspot']).default('custom'),
118
+ label: text,
119
+ displayOrder: z.number().int().optional(),
120
+ stages: draft ? z.array(StageSchema) : z.array(StageSchema).min(1, 'a pipeline needs at least one stage'),
121
+ });
122
+ const ObjectSchema = z.strictObject({
123
+ objectType: identifier,
124
+ schema: CustomObjectSchema.optional(),
125
+ /** Standard objects only: the names HubSpot's were changed to in the portal. */
126
+ labels: z.strictObject({ singular: text, plural: text }).optional(),
127
+ propertyGroups: z.array(PropertyGroupSchema).default([]),
128
+ properties: z.array(PropertySchema).default([]),
129
+ pipelines: z.array(PipelineSchema).default([]),
130
+ });
131
+ /** A label of a pair. `label` reads from the pair's `from` side, `inverseLabel` from its `to` side. */
132
+ const AssociationLabelSchema = z.strictObject({
133
+ /** Stable, portal-agnostic identity; also the write-only internal `name` sent on create. */
134
+ key: identifier,
135
+ label: text,
136
+ inverseLabel: text.optional(),
137
+ /** Most `to` records one `from` record can have through this label. Absent: no limit. */
138
+ limit: limit.optional(),
139
+ inverseLimit: limit.optional(),
140
+ });
141
+ /** One entry per unordered object pair; a self-pair (from = to) is allowed. */
142
+ const AssociationSchema = z.strictObject({
143
+ from: identifier,
144
+ to: identifier,
145
+ /** Limits of the unlabelled association, per direction. Absent: no limit (not unmanaged). */
146
+ limit: limit.optional(),
147
+ inverseLimit: limit.optional(),
148
+ labels: z.array(AssociationLabelSchema).default([]),
149
+ });
150
+ const BlueprintSchema = z.strictObject({
151
+ format: z.literal(BLUEPRINT_FORMAT),
152
+ source: z.strictObject({ portalId: z.number().int(), ingestedAt: z.string() }).optional(),
153
+ objects: z.array(ObjectSchema),
154
+ associations: z.array(AssociationSchema).default([]),
155
+ });
156
+ return {
157
+ OptionSchema, PropertySchema, PropertyGroupSchema, CustomObjectSchema, StageMetadataSchema, StageSchema,
158
+ PipelineSchema, ObjectSchema, AssociationLabelSchema, AssociationSchema, BlueprintSchema,
159
+ };
160
+ }
161
+ export const { OptionSchema, PropertySchema, PropertyGroupSchema, CustomObjectSchema, StageMetadataSchema, StageSchema, PipelineSchema, ObjectSchema, AssociationLabelSchema, AssociationSchema, BlueprintSchema, } = buildSchemas(false);
162
+ /** What the editor's forms can render (spec #27 §4.3). Never used to accept a version. */
163
+ export const DraftBlueprintSchema = buildSchemas(true).BlueprintSchema;