@bacano/sdk 1.2.2 → 1.2.4

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.
@@ -2,7 +2,7 @@ import type { FormManagementClient } from "./form-management-types";
2
2
  export type ContentManagementTypeKind = "entry" | "component";
3
3
  export type ContentManagementTypeStatus = "draft" | "active" | "archived";
4
4
  export type ContentManagementTypeUsage = "website_content" | "product_additional" | "product_specification";
5
- export type ContentManagementScope = "content_types:manage" | "content_entries:manage" | "content_media:upload" | "content_entries:publish" | "forms:manage" | "forms:publish";
5
+ export type ContentManagementScope = "content_types:manage" | "content_entries:manage" | "content_media:upload" | "content_entries:publish" | "forms:manage" | "forms:publish" | "product_content:manage";
6
6
  export type ContentManagementFieldType = "boolean" | "component" | "date" | "link" | "measure" | "media" | "number" | "reference" | "repeater" | "rich_text" | "rich_text_full" | "select" | "slug" | "text" | "textarea";
7
7
  export interface ContentManagementChildFieldInput {
8
8
  id?: string | null;
@@ -68,8 +68,46 @@ export interface ContentManagementContext {
68
68
  /** @deprecated Use scopes to inspect the complete grant. */
69
69
  scope: ContentManagementScope;
70
70
  scopes: ContentManagementScope[];
71
+ productIds: string[];
71
72
  expiresAt: string;
72
73
  }
74
+ export interface ContentManagementProductBlock {
75
+ id: string;
76
+ productId: string;
77
+ contentType: {
78
+ id: string;
79
+ key: string;
80
+ name: string;
81
+ };
82
+ locale: string;
83
+ content: Record<string, unknown>;
84
+ sortOrder: number;
85
+ createdAt: string;
86
+ updatedAt: string;
87
+ }
88
+ export interface ContentManagementProductBlockListInput {
89
+ contentType?: string;
90
+ locale?: string;
91
+ }
92
+ export interface ContentManagementProductBlockCreateInput {
93
+ contentType: string;
94
+ locale?: string;
95
+ content: Record<string, unknown>;
96
+ sortOrder: number;
97
+ }
98
+ export interface ContentManagementProductBlockUpdateInput {
99
+ content: Record<string, unknown>;
100
+ sortOrder: number;
101
+ expectedUpdatedAt: string;
102
+ }
103
+ export interface ContentManagementProductBlockReorderInput {
104
+ locale: string;
105
+ /** Complete ordered list for this product and locale. */
106
+ items: Array<{
107
+ id: string;
108
+ expectedUpdatedAt: string;
109
+ }>;
110
+ }
73
111
  export type ContentManagementEntryStatus = "draft" | "published" | "archived";
74
112
  export interface ContentManagementEntryInput {
75
113
  contentType: string;
@@ -262,6 +300,11 @@ export interface ContentManagementClientConfig {
262
300
  }
263
301
  export interface ContentManagementClient extends FormManagementClient {
264
302
  getContext(): Promise<ContentManagementContext>;
303
+ listProductBlocks(productId: string, input?: ContentManagementProductBlockListInput): Promise<ContentManagementProductBlock[]>;
304
+ createProductBlock(productId: string, input: ContentManagementProductBlockCreateInput): Promise<ContentManagementProductBlock>;
305
+ updateProductBlock(productId: string, blockId: string, input: ContentManagementProductBlockUpdateInput): Promise<ContentManagementProductBlock>;
306
+ reorderProductBlocks(productId: string, input: ContentManagementProductBlockReorderInput): Promise<ContentManagementProductBlock[]>;
307
+ retireProductBlock(productId: string, blockId: string, expectedUpdatedAt: string): Promise<ContentManagementProductBlock>;
265
308
  getReferences(): Promise<ContentManagementReferences>;
266
309
  listTypes(): Promise<ContentManagementType[]>;
267
310
  getType(identifier: string): Promise<ContentManagementType>;
@@ -112,6 +112,13 @@ function mediaListPath(input = {}) {
112
112
  const query = search.toString();
113
113
  return query ? `/media?${query}` : "/media";
114
114
  }
115
+ function productBlocksPath(productId, input = {}) {
116
+ const path = `/products/${encodeIdentifier(productId)}/blocks`;
117
+ const search = new URLSearchParams();
118
+ if (input.contentType) search.set("contentType", input.contentType);
119
+ if (input.locale) search.set("locale", input.locale);
120
+ return search.size ? `${path}?${search}` : path;
121
+ }
115
122
  function uploadBody(file) {
116
123
  if ("arrayBuffer" in file) return file;
117
124
  if (file instanceof ArrayBuffer) return file;
@@ -183,6 +190,25 @@ function createContentManagementClient(config) {
183
190
  return {
184
191
  ...createFormManagementMethods(request),
185
192
  getContext: () => request("/context"),
193
+ listProductBlocks: (productId, input) => request(
194
+ productBlocksPath(productId, input)
195
+ ),
196
+ createProductBlock: (productId, input) => request(productBlocksPath(productId), {
197
+ method: "POST",
198
+ body: JSON.stringify(input)
199
+ }),
200
+ updateProductBlock: (productId, blockId, input) => request(
201
+ `${productBlocksPath(productId)}/${encodeIdentifier(blockId)}`,
202
+ { method: "PUT", body: JSON.stringify(input) }
203
+ ),
204
+ reorderProductBlocks: (productId, input) => request(
205
+ `${productBlocksPath(productId)}/order`,
206
+ { method: "PUT", body: JSON.stringify(input) }
207
+ ),
208
+ retireProductBlock: (productId, blockId, expectedUpdatedAt) => request(
209
+ `${productBlocksPath(productId)}/${encodeIdentifier(blockId)}`,
210
+ { method: "DELETE", body: JSON.stringify({ expectedUpdatedAt }) }
211
+ ),
186
212
  getReferences: () => request("/references"),
187
213
  listTypes: () => request("/types"),
188
214
  getType: (identifier) => request(`/types/${encodeIdentifier(identifier)}`),
@@ -85,6 +85,13 @@ function mediaListPath(input = {}) {
85
85
  const query = search.toString();
86
86
  return query ? `/media?${query}` : "/media";
87
87
  }
88
+ function productBlocksPath(productId, input = {}) {
89
+ const path = `/products/${encodeIdentifier(productId)}/blocks`;
90
+ const search = new URLSearchParams();
91
+ if (input.contentType) search.set("contentType", input.contentType);
92
+ if (input.locale) search.set("locale", input.locale);
93
+ return search.size ? `${path}?${search}` : path;
94
+ }
88
95
  function uploadBody(file) {
89
96
  if ("arrayBuffer" in file) return file;
90
97
  if (file instanceof ArrayBuffer) return file;
@@ -156,6 +163,25 @@ function createContentManagementClient(config) {
156
163
  return {
157
164
  ...createFormManagementMethods(request),
158
165
  getContext: () => request("/context"),
166
+ listProductBlocks: (productId, input) => request(
167
+ productBlocksPath(productId, input)
168
+ ),
169
+ createProductBlock: (productId, input) => request(productBlocksPath(productId), {
170
+ method: "POST",
171
+ body: JSON.stringify(input)
172
+ }),
173
+ updateProductBlock: (productId, blockId, input) => request(
174
+ `${productBlocksPath(productId)}/${encodeIdentifier(blockId)}`,
175
+ { method: "PUT", body: JSON.stringify(input) }
176
+ ),
177
+ reorderProductBlocks: (productId, input) => request(
178
+ `${productBlocksPath(productId)}/order`,
179
+ { method: "PUT", body: JSON.stringify(input) }
180
+ ),
181
+ retireProductBlock: (productId, blockId, expectedUpdatedAt) => request(
182
+ `${productBlocksPath(productId)}/${encodeIdentifier(blockId)}`,
183
+ { method: "DELETE", body: JSON.stringify({ expectedUpdatedAt }) }
184
+ ),
159
185
  getReferences: () => request("/references"),
160
186
  listTypes: () => request("/types"),
161
187
  getType: (identifier) => request(`/types/${encodeIdentifier(identifier)}`),
package/flows.d.ts ADDED
@@ -0,0 +1,109 @@
1
+ import type { AccessTokenProvider, WebsiteFormDefinition, WebsiteFormResponseValue } from "./types";
2
+ export interface FlowCaseSummary {
3
+ id: string;
4
+ title: string;
5
+ reference: string;
6
+ status: "open" | "completed" | "rejected";
7
+ flowName: string;
8
+ }
9
+ export interface ParticipantFlowStep {
10
+ id: string;
11
+ kind: "content" | "form" | "task" | "review";
12
+ title: string;
13
+ required: boolean;
14
+ completed: boolean;
15
+ available: boolean;
16
+ canComplete: boolean;
17
+ internal: boolean;
18
+ canReadResponse: boolean;
19
+ completion?: "reference" | "continue" | "confirmation";
20
+ confirmationText?: string;
21
+ instructions?: string;
22
+ }
23
+ export interface ParticipantJourney extends FlowCaseSummary {
24
+ revision: number;
25
+ flowRevision: number;
26
+ stageId: string;
27
+ dueDate: string | null;
28
+ stages: Array<{
29
+ id: string;
30
+ name: string;
31
+ current: boolean;
32
+ /** Presentation only; availability and completion remain server-authoritative. */
33
+ presentation?: "step_by_step" | "single_view";
34
+ steps: ParticipantFlowStep[];
35
+ }>;
36
+ messages: Array<{
37
+ createdAt: string;
38
+ message: string;
39
+ }>;
40
+ }
41
+ export interface FlowPublishedForm {
42
+ name: string;
43
+ version: number;
44
+ definitionHash: string;
45
+ definition: WebsiteFormDefinition;
46
+ /** Own previous answers, scoped and filtered by the backend for a correction. */
47
+ initialValues?: Record<string, WebsiteFormResponseValue>;
48
+ correction?: {
49
+ attempt: number;
50
+ formChanged: boolean;
51
+ };
52
+ }
53
+ export interface FlowContentResource {
54
+ id: string;
55
+ name: string;
56
+ content: Record<string, unknown>;
57
+ }
58
+ export interface FlowResponse {
59
+ snapshot: Array<{
60
+ key: string;
61
+ label: string;
62
+ displayValue: unknown;
63
+ }>;
64
+ attempt: number;
65
+ submittedAt: string;
66
+ }
67
+ export interface FlowParticipantCommand {
68
+ stepId: string;
69
+ revision: number;
70
+ flowRevision: number;
71
+ idempotencyKey: string;
72
+ operation: "complete_content" | "complete_task" | "submit_form";
73
+ confirmed?: boolean;
74
+ submission?: {
75
+ formVersion: number;
76
+ definitionHash: string;
77
+ values: Record<string, WebsiteFormResponseValue>;
78
+ };
79
+ }
80
+ export interface FlowsClientConfig {
81
+ apiUrl: string;
82
+ getAccessToken?: AccessTokenProvider;
83
+ websiteSlug?: string;
84
+ }
85
+ export interface FlowDemoSession {
86
+ token: string;
87
+ name: string;
88
+ expiresAt: string;
89
+ }
90
+ /** Gateway only. Tokens are supplied per request; this client stores no credentials. */
91
+ export declare function createFlowsClient(config: FlowsClientConfig): {
92
+ /** Only the explicitly enabled local demo gateway accepts these credentials. */
93
+ localDemoLogin: (email: string, password: string) => Promise<FlowDemoSession>;
94
+ logout: () => Promise<{
95
+ success: boolean;
96
+ }>;
97
+ listCases: () => Promise<FlowCaseSummary[]>;
98
+ getJourney: (caseId: string) => Promise<ParticipantJourney>;
99
+ getContent: (caseId: string, stepId: string) => Promise<FlowContentResource[]>;
100
+ getForm: (caseId: string, stepId: string) => Promise<FlowPublishedForm>;
101
+ getResponse: (caseId: string, stepId: string) => Promise<FlowResponse>;
102
+ getFile: (caseId: string, stepId: string, fileId: string) => Promise<Blob>;
103
+ act: (caseId: string, command: FlowParticipantCommand) => Promise<{
104
+ caseId: string;
105
+ revision: number;
106
+ responseId: string | null;
107
+ }>;
108
+ };
109
+ export type FlowsClient = ReturnType<typeof createFlowsClient>;
@@ -0,0 +1,6 @@
1
+ import type { WebsiteFormDefinition, WebsiteFormField } from "./types";
2
+ export declare function formRootFields(definition: WebsiteFormDefinition): WebsiteFormField[];
3
+ /** Unselected branches do not participate in validation or submissions. */
4
+ export declare function visibleFormFields(fields: WebsiteFormField[], values: Record<string, unknown>): WebsiteFormField[];
5
+ export declare function allFormFields(fields: WebsiteFormField[]): WebsiteFormField[];
6
+ export declare function activeFormValues<T>(definition: WebsiteFormDefinition, values: Record<string, T>): Record<string, T>;
package/index.cjs CHANGED
@@ -30,6 +30,7 @@ __export(index_exports, {
30
30
  RateLimitError: () => RateLimitError,
31
31
  WEBSITE_MARKETING_CONTACT_PLACEMENTS: () => WEBSITE_MARKETING_CONTACT_PLACEMENTS,
32
32
  WebsiteError: () => WebsiteError,
33
+ activeFormValues: () => activeFormValues,
33
34
  buildStorageContentMediaUrls: () => buildStorageContentMediaUrls,
34
35
  buildStorageFileUrl: () => buildStorageFileUrl,
35
36
  buildStorageMediaUrls: () => buildStorageMediaUrls,
@@ -37,7 +38,9 @@ __export(index_exports, {
37
38
  buildStorageTransformedFileUrl: () => buildStorageTransformedFileUrl,
38
39
  captureWebsiteMarketingAttribution: () => captureWebsiteMarketingAttribution,
39
40
  createBacanoClient: () => createBacanoClient,
41
+ createFlowsClient: () => createFlowsClient,
40
42
  createWebsiteMarketingRuntime: () => createWebsiteMarketingRuntime,
43
+ formRootFields: () => formRootFields,
41
44
  formatMediaSrcSet: () => formatMediaSrcSet,
42
45
  getAllowedMarketingParameters: () => getAllowedMarketingParameters,
43
46
  getContentEntryLocatorAttributes: () => getContentEntryLocatorAttributes,
@@ -51,7 +54,8 @@ __export(index_exports, {
51
54
  getWebsiteMarketingAttribution: () => getWebsiteMarketingAttribution,
52
55
  getWebsitePublicConfig: () => getWebsitePublicConfig,
53
56
  resolveProductPricing: () => resolveProductPricing,
54
- sanitizeMarketingUrl: () => sanitizeMarketingUrl
57
+ sanitizeMarketingUrl: () => sanitizeMarketingUrl,
58
+ visibleFormFields: () => visibleFormFields
55
59
  });
56
60
  module.exports = __toCommonJS(index_exports);
57
61
 
@@ -110,6 +114,52 @@ var GraphQLRequestError = class extends WebsiteError {
110
114
  }
111
115
  };
112
116
 
117
+ // libs/sdk/src/flows.ts
118
+ function createFlowsClient(config) {
119
+ const base = config.apiUrl.replace(/\/+$/, "") + "/api/v1/website/flows";
120
+ async function request(path, body, binary = false) {
121
+ const token = await config.getAccessToken?.();
122
+ const response = await fetch(base + path, {
123
+ method: body === void 0 ? "GET" : "POST",
124
+ cache: "no-store",
125
+ headers: {
126
+ ...token ? { Authorization: `Bearer ${token}` } : {},
127
+ ...body !== void 0 ? { "Content-Type": "application/json" } : {},
128
+ ...config.websiteSlug ? { "x-bacano-website-slug": config.websiteSlug } : {}
129
+ },
130
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
131
+ });
132
+ if (!response.ok) {
133
+ const result = await response.json().catch(() => ({ error: "FLOW_REQUEST_FAILED" }));
134
+ throw new WebsiteError(
135
+ result.error ?? "FLOW_REQUEST_FAILED",
136
+ result.error ?? "FLOW_REQUEST_FAILED"
137
+ );
138
+ }
139
+ return binary ? await response.blob() : await response.json();
140
+ }
141
+ const stepPath = (caseId, stepId) => `/cases/${encodeURIComponent(caseId)}/steps/${encodeURIComponent(stepId)}`;
142
+ return {
143
+ /** Only the explicitly enabled local demo gateway accepts these credentials. */
144
+ localDemoLogin: (email, password) => request("/local-session", { email, password }),
145
+ logout: () => request("/logout", {}),
146
+ listCases: () => request("/cases"),
147
+ getJourney: (caseId) => request(`/cases/${encodeURIComponent(caseId)}`),
148
+ getContent: (caseId, stepId) => request(stepPath(caseId, stepId) + "/content"),
149
+ getForm: (caseId, stepId) => request(stepPath(caseId, stepId) + "/form"),
150
+ getResponse: (caseId, stepId) => request(stepPath(caseId, stepId) + "/response"),
151
+ getFile: (caseId, stepId, fileId) => request(
152
+ stepPath(caseId, stepId) + `/files/${encodeURIComponent(fileId)}`,
153
+ void 0,
154
+ true
155
+ ),
156
+ act: (caseId, command) => request(
157
+ `/cases/${encodeURIComponent(caseId)}/actions`,
158
+ command
159
+ )
160
+ };
161
+ }
162
+
113
163
  // libs/sdk/src/media.ts
114
164
  var DEFAULT_RESPONSIVE_WIDTHS = [240, 384, 640, 960, 1400];
115
165
  var CONTENT_HERO_WIDTHS = [640, 960, 1400, 1920, 2560];
@@ -1364,6 +1414,35 @@ var WEBSITE_GET_PRODUCT_CONTENT = (
1364
1414
  }
1365
1415
  `
1366
1416
  );
1417
+ var WEBSITE_GET_PRODUCT_CONTENT_BATCH = (
1418
+ /* GraphQL */
1419
+ `
1420
+ mutation WebsiteGetProductContentBatch(
1421
+ $websiteId: uuid!
1422
+ $productIds: [uuid!]!
1423
+ $locale: String
1424
+ ) {
1425
+ result: websiteGetProductContentBatch(
1426
+ website_id: $websiteId
1427
+ product_ids: $productIds
1428
+ locale: $locale
1429
+ ) {
1430
+ success
1431
+ errors
1432
+ blocks {
1433
+ id
1434
+ product_id
1435
+ locale
1436
+ content_json
1437
+ resolved_fields
1438
+ sort_order
1439
+ updated_at
1440
+ content_type { id key name kind }
1441
+ }
1442
+ }
1443
+ }
1444
+ `
1445
+ );
1367
1446
  var CHECK_AVAILABILITY = (
1368
1447
  /* GraphQL */
1369
1448
  `
@@ -2240,7 +2319,7 @@ var RECONCILE_PAYMENT_SESSION = (
2240
2319
  $orderId: uuid!
2241
2320
  $publicOrderToken: String!
2242
2321
  $paymentSessionId: uuid!
2243
- $gatewayTransactionId: String!
2322
+ $gatewayTransactionId: String
2244
2323
  ) {
2245
2324
  websiteReconcilePaymentSession(
2246
2325
  order_id: $orderId
@@ -5732,6 +5811,33 @@ function createContentModule(graphqlUrl, getHeaders, getContext, storageUrl) {
5732
5811
  return (data.result.blocks ?? []).map(
5733
5812
  (block) => mapProductContentBlock(block, storageUrl)
5734
5813
  );
5814
+ },
5815
+ async getForProducts(productIds, opts) {
5816
+ const uniqueIds = [...new Set(productIds.map((id) => id.toLowerCase()))];
5817
+ const blocksByProduct = Object.fromEntries(uniqueIds.map((id) => [id, []]));
5818
+ if (uniqueIds.length === 0) return blocksByProduct;
5819
+ const ctx = getContext();
5820
+ for (let start = 0; start < uniqueIds.length; start += 50) {
5821
+ const batch = uniqueIds.slice(start, start + 50);
5822
+ const data = await executeGraphQL(
5823
+ graphqlUrl,
5824
+ WEBSITE_GET_PRODUCT_CONTENT_BATCH,
5825
+ {
5826
+ websiteId: ctx.websiteId,
5827
+ productIds: batch,
5828
+ locale: opts?.locale ?? DEFAULT_LOCALE
5829
+ },
5830
+ getHeaders
5831
+ );
5832
+ if (!data.result?.success) continue;
5833
+ for (const block of data.result.blocks ?? []) {
5834
+ const id = block.product_id.toLowerCase();
5835
+ if (Object.prototype.hasOwnProperty.call(blocksByProduct, id)) {
5836
+ blocksByProduct[id].push(mapProductContentBlock(block, storageUrl));
5837
+ }
5838
+ }
5839
+ }
5840
+ return blocksByProduct;
5735
5841
  }
5736
5842
  };
5737
5843
  }
@@ -7064,6 +7170,7 @@ function createBacanoClient(config) {
7064
7170
  await initialization;
7065
7171
  },
7066
7172
  getContext,
7173
+ flows: createFlowsClient(config),
7067
7174
  auth: {
7068
7175
  async getAccessToken() {
7069
7176
  return config.getAccessToken ? await config.getAccessToken() : null;
@@ -7096,7 +7203,8 @@ function createBacanoClient(config) {
7096
7203
  getByKey: contentModule.getByKey.bind(contentModule),
7097
7204
  getByCategory: contentModule.getByCategory.bind(contentModule),
7098
7205
  getByTag: contentModule.getByTag.bind(contentModule),
7099
- getForProduct: contentModule.getForProduct.bind(contentModule)
7206
+ getForProduct: contentModule.getForProduct.bind(contentModule),
7207
+ getForProducts: contentModule.getForProducts.bind(contentModule)
7100
7208
  },
7101
7209
  forms: {
7102
7210
  getDefinition: formsModule.getDefinition.bind(formsModule),
@@ -7163,3 +7271,34 @@ function getContentEntryLocatorAttributes(entry) {
7163
7271
  "data-bacano-entry-name": entry.name.trim() || key
7164
7272
  };
7165
7273
  }
7274
+
7275
+ // libs/sdk/src/form-conditions.ts
7276
+ function formRootFields(definition) {
7277
+ return [
7278
+ ...definition.fields,
7279
+ ...definition.sections.flatMap((section) => section.fields),
7280
+ ...definition.steps.flatMap((step) => [
7281
+ ...step.fields,
7282
+ ...step.sections.flatMap((section) => section.fields)
7283
+ ])
7284
+ ];
7285
+ }
7286
+ function visibleFormFields(fields, values) {
7287
+ return fields.flatMap((field) => [
7288
+ field,
7289
+ ...visibleFormFields(
7290
+ field.branches?.find((branch) => branch.value === values[field.key])?.fields ?? [],
7291
+ values
7292
+ )
7293
+ ]);
7294
+ }
7295
+ function activeFormValues(definition, values) {
7296
+ const keys = new Set(
7297
+ visibleFormFields(formRootFields(definition), values).map(
7298
+ (field) => field.key
7299
+ )
7300
+ );
7301
+ return Object.fromEntries(
7302
+ Object.entries(values).filter(([key]) => keys.has(key))
7303
+ );
7304
+ }
package/index.d.ts CHANGED
@@ -3,6 +3,9 @@ export { createBacanoClient } from "./client";
3
3
  export type { ContentEntryLocatorAttributes, ContentEntryLocatorSource, } from "./content-entry-locator";
4
4
  export { CONTENT_ENTRY_LOCATOR_SELECTOR, CONTENT_ENTRY_LOCATOR_VERSION, getContentEntryLocatorAttributes, } from "./content-entry-locator";
5
5
  export { AuthError, CheckoutError, GraphQLRequestError, NotInitializedError, RateLimitError, WebsiteError, } from "./errors";
6
+ export type { FlowCaseSummary, FlowContentResource, FlowDemoSession, FlowParticipantCommand, FlowPublishedForm, FlowResponse, FlowsClient, FlowsClientConfig, ParticipantFlowStep, ParticipantJourney, } from "./flows";
7
+ export { createFlowsClient } from "./flows";
8
+ export { activeFormValues, formRootFields, visibleFormFields, } from "./form-conditions";
6
9
  export type { WebsiteAdvertisingConsent, WebsiteMarketingAttribution, WebsiteMarketingBrowserEvent, WebsiteMarketingBrowserEventName, WebsiteMarketingContactPlacement, WebsiteMarketingDiagnostic, WebsiteMarketingItem, WebsiteMarketingRuntime, WebsiteMarketingRuntimeOptions, WebsiteMarketingScriptLoadStrategy, WebsiteMarketingTouchpoint, } from "./marketing";
7
10
  export { captureWebsiteMarketingAttribution, createWebsiteMarketingRuntime, getAllowedMarketingParameters, getWebsiteMarketingAttribution, MARKETING_SCHEMA_VERSION, sanitizeMarketingUrl, WEBSITE_MARKETING_CONTACT_PLACEMENTS, } from "./marketing";
8
11
  export type { MediaResponsiveSource, MediaResponsiveSourceOptions, MediaTransformFormat, MediaTransformOptions, MediaUrlVariant, } from "./media";