@bacano/sdk 1.2.2 → 1.2.3

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/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];
@@ -2240,7 +2290,7 @@ var RECONCILE_PAYMENT_SESSION = (
2240
2290
  $orderId: uuid!
2241
2291
  $publicOrderToken: String!
2242
2292
  $paymentSessionId: uuid!
2243
- $gatewayTransactionId: String!
2293
+ $gatewayTransactionId: String
2244
2294
  ) {
2245
2295
  websiteReconcilePaymentSession(
2246
2296
  order_id: $orderId
@@ -7064,6 +7114,7 @@ function createBacanoClient(config) {
7064
7114
  await initialization;
7065
7115
  },
7066
7116
  getContext,
7117
+ flows: createFlowsClient(config),
7067
7118
  auth: {
7068
7119
  async getAccessToken() {
7069
7120
  return config.getAccessToken ? await config.getAccessToken() : null;
@@ -7163,3 +7214,34 @@ function getContentEntryLocatorAttributes(entry) {
7163
7214
  "data-bacano-entry-name": entry.name.trim() || key
7164
7215
  };
7165
7216
  }
7217
+
7218
+ // libs/sdk/src/form-conditions.ts
7219
+ function formRootFields(definition) {
7220
+ return [
7221
+ ...definition.fields,
7222
+ ...definition.sections.flatMap((section) => section.fields),
7223
+ ...definition.steps.flatMap((step) => [
7224
+ ...step.fields,
7225
+ ...step.sections.flatMap((section) => section.fields)
7226
+ ])
7227
+ ];
7228
+ }
7229
+ function visibleFormFields(fields, values) {
7230
+ return fields.flatMap((field) => [
7231
+ field,
7232
+ ...visibleFormFields(
7233
+ field.branches?.find((branch) => branch.value === values[field.key])?.fields ?? [],
7234
+ values
7235
+ )
7236
+ ]);
7237
+ }
7238
+ function activeFormValues(definition, values) {
7239
+ const keys = new Set(
7240
+ visibleFormFields(formRootFields(definition), values).map(
7241
+ (field) => field.key
7242
+ )
7243
+ );
7244
+ return Object.fromEntries(
7245
+ Object.entries(values).filter(([key]) => keys.has(key))
7246
+ );
7247
+ }
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";
package/index.js CHANGED
@@ -53,6 +53,52 @@ var GraphQLRequestError = class extends WebsiteError {
53
53
  }
54
54
  };
55
55
 
56
+ // libs/sdk/src/flows.ts
57
+ function createFlowsClient(config) {
58
+ const base = config.apiUrl.replace(/\/+$/, "") + "/api/v1/website/flows";
59
+ async function request(path, body, binary = false) {
60
+ const token = await config.getAccessToken?.();
61
+ const response = await fetch(base + path, {
62
+ method: body === void 0 ? "GET" : "POST",
63
+ cache: "no-store",
64
+ headers: {
65
+ ...token ? { Authorization: `Bearer ${token}` } : {},
66
+ ...body !== void 0 ? { "Content-Type": "application/json" } : {},
67
+ ...config.websiteSlug ? { "x-bacano-website-slug": config.websiteSlug } : {}
68
+ },
69
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
70
+ });
71
+ if (!response.ok) {
72
+ const result = await response.json().catch(() => ({ error: "FLOW_REQUEST_FAILED" }));
73
+ throw new WebsiteError(
74
+ result.error ?? "FLOW_REQUEST_FAILED",
75
+ result.error ?? "FLOW_REQUEST_FAILED"
76
+ );
77
+ }
78
+ return binary ? await response.blob() : await response.json();
79
+ }
80
+ const stepPath = (caseId, stepId) => `/cases/${encodeURIComponent(caseId)}/steps/${encodeURIComponent(stepId)}`;
81
+ return {
82
+ /** Only the explicitly enabled local demo gateway accepts these credentials. */
83
+ localDemoLogin: (email, password) => request("/local-session", { email, password }),
84
+ logout: () => request("/logout", {}),
85
+ listCases: () => request("/cases"),
86
+ getJourney: (caseId) => request(`/cases/${encodeURIComponent(caseId)}`),
87
+ getContent: (caseId, stepId) => request(stepPath(caseId, stepId) + "/content"),
88
+ getForm: (caseId, stepId) => request(stepPath(caseId, stepId) + "/form"),
89
+ getResponse: (caseId, stepId) => request(stepPath(caseId, stepId) + "/response"),
90
+ getFile: (caseId, stepId, fileId) => request(
91
+ stepPath(caseId, stepId) + `/files/${encodeURIComponent(fileId)}`,
92
+ void 0,
93
+ true
94
+ ),
95
+ act: (caseId, command) => request(
96
+ `/cases/${encodeURIComponent(caseId)}/actions`,
97
+ command
98
+ )
99
+ };
100
+ }
101
+
56
102
  // libs/sdk/src/media.ts
57
103
  var DEFAULT_RESPONSIVE_WIDTHS = [240, 384, 640, 960, 1400];
58
104
  var CONTENT_HERO_WIDTHS = [640, 960, 1400, 1920, 2560];
@@ -2183,7 +2229,7 @@ var RECONCILE_PAYMENT_SESSION = (
2183
2229
  $orderId: uuid!
2184
2230
  $publicOrderToken: String!
2185
2231
  $paymentSessionId: uuid!
2186
- $gatewayTransactionId: String!
2232
+ $gatewayTransactionId: String
2187
2233
  ) {
2188
2234
  websiteReconcilePaymentSession(
2189
2235
  order_id: $orderId
@@ -7007,6 +7053,7 @@ function createBacanoClient(config) {
7007
7053
  await initialization;
7008
7054
  },
7009
7055
  getContext,
7056
+ flows: createFlowsClient(config),
7010
7057
  auth: {
7011
7058
  async getAccessToken() {
7012
7059
  return config.getAccessToken ? await config.getAccessToken() : null;
@@ -7106,6 +7153,37 @@ function getContentEntryLocatorAttributes(entry) {
7106
7153
  "data-bacano-entry-name": entry.name.trim() || key
7107
7154
  };
7108
7155
  }
7156
+
7157
+ // libs/sdk/src/form-conditions.ts
7158
+ function formRootFields(definition) {
7159
+ return [
7160
+ ...definition.fields,
7161
+ ...definition.sections.flatMap((section) => section.fields),
7162
+ ...definition.steps.flatMap((step) => [
7163
+ ...step.fields,
7164
+ ...step.sections.flatMap((section) => section.fields)
7165
+ ])
7166
+ ];
7167
+ }
7168
+ function visibleFormFields(fields, values) {
7169
+ return fields.flatMap((field) => [
7170
+ field,
7171
+ ...visibleFormFields(
7172
+ field.branches?.find((branch) => branch.value === values[field.key])?.fields ?? [],
7173
+ values
7174
+ )
7175
+ ]);
7176
+ }
7177
+ function activeFormValues(definition, values) {
7178
+ const keys = new Set(
7179
+ visibleFormFields(formRootFields(definition), values).map(
7180
+ (field) => field.key
7181
+ )
7182
+ );
7183
+ return Object.fromEntries(
7184
+ Object.entries(values).filter(([key]) => keys.has(key))
7185
+ );
7186
+ }
7109
7187
  export {
7110
7188
  AuthError,
7111
7189
  CONTENT_ENTRY_LOCATOR_SELECTOR,
@@ -7117,6 +7195,7 @@ export {
7117
7195
  RateLimitError,
7118
7196
  WEBSITE_MARKETING_CONTACT_PLACEMENTS,
7119
7197
  WebsiteError,
7198
+ activeFormValues,
7120
7199
  buildStorageContentMediaUrls,
7121
7200
  buildStorageFileUrl,
7122
7201
  buildStorageMediaUrls,
@@ -7124,7 +7203,9 @@ export {
7124
7203
  buildStorageTransformedFileUrl,
7125
7204
  captureWebsiteMarketingAttribution,
7126
7205
  createBacanoClient,
7206
+ createFlowsClient,
7127
7207
  createWebsiteMarketingRuntime,
7208
+ formRootFields,
7128
7209
  formatMediaSrcSet,
7129
7210
  getAllowedMarketingParameters,
7130
7211
  getContentEntryLocatorAttributes,
@@ -7138,5 +7219,6 @@ export {
7138
7219
  getWebsiteMarketingAttribution,
7139
7220
  getWebsitePublicConfig,
7140
7221
  resolveProductPricing,
7141
- sanitizeMarketingUrl
7222
+ sanitizeMarketingUrl,
7223
+ visibleFormFields
7142
7224
  };
@@ -36,7 +36,7 @@ export interface CheckoutModule {
36
36
  orderId: string;
37
37
  publicOrderToken: string;
38
38
  paymentSessionId: string;
39
- gatewayTransactionId: string;
39
+ gatewayTransactionId?: string;
40
40
  }): Promise<WebsitePaymentSessionResult>;
41
41
  }
42
42
  export declare function createCheckoutModule(graphqlUrl: string, getHeaders: RequestHeadersProvider, getContext: () => BacanoClientContext, cartSessionStorage: CartSessionStorage): CheckoutModule;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bacano/sdk",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "TypeScript SDK for building websites on the Bacano ERP",
5
5
  "main": "./index.cjs",
6
6
  "module": "./index.js",
@@ -0,0 +1,9 @@
1
+ import type { FlowPublishedForm } from "../flows";
2
+ import type { BacanoFormClassNames, BacanoFormValues } from "./forms";
3
+ /** Reuses the Forms controls but never calls a website form-entry endpoint. */
4
+ export declare function BacanoFlowForm({ form, onSubmit, onRefresh, classNames, }: {
5
+ form: FlowPublishedForm;
6
+ onSubmit(values: BacanoFormValues, form: FlowPublishedForm): Promise<void>;
7
+ onRefresh(): Promise<void>;
8
+ classNames?: BacanoFormClassNames;
9
+ }): import("react").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import type { WebsiteFormField } from "../types";
2
+ /** Discrete radios preserve keyboard support, required validation and an empty initial answer. */
3
+ export declare function FormScaleControl({ field, id, value, onChange, className, }: {
4
+ field: WebsiteFormField;
5
+ id: string;
6
+ value: string | string[] | boolean;
7
+ onChange(value: string): void;
8
+ className?: string;
9
+ }): import("react").JSX.Element | null;