@funnelsgrove/runtime 0.1.56 → 0.1.60

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 (56) hide show
  1. package/dist/components/FunnelContext.d.ts +5 -1
  2. package/dist/components/FunnelContext.js +2 -1
  3. package/dist/components/ManageSubscriptionScreen.js +4 -1
  4. package/dist/components/SubscriptionHandoffScreen.d.ts +2 -1
  5. package/dist/components/SubscriptionHandoffScreen.js +11 -6
  6. package/dist/config/funnel.manifest.types.d.ts +4 -2
  7. package/dist/generated/step-contract-hash.d.ts +1 -0
  8. package/dist/generated/step-contract-hash.js +2 -0
  9. package/dist/index.d.ts +12 -0
  10. package/dist/index.js +10 -0
  11. package/dist/migrations/step-contract-v2.d.ts +50 -0
  12. package/dist/migrations/step-contract-v2.js +310 -0
  13. package/dist/migrations/step-contract-v3.d.ts +41 -0
  14. package/dist/migrations/step-contract-v3.js +26 -0
  15. package/dist/runtime/funnel-manifest.validation.d.ts +7 -2
  16. package/dist/runtime/funnel-manifest.validation.js +24 -68
  17. package/dist/runtime/funnel-step-lifecycle.d.ts +35 -0
  18. package/dist/runtime/funnel-step-lifecycle.js +49 -0
  19. package/dist/runtime/funnel-step-metadata.validation.d.ts +7 -0
  20. package/dist/runtime/funnel-step-metadata.validation.js +88 -0
  21. package/dist/runtime/offer-set-runtime.d.ts +17 -0
  22. package/dist/runtime/offer-set-runtime.js +18 -6
  23. package/dist/runtime/submit-email-capture.d.ts +23 -0
  24. package/dist/runtime/submit-email-capture.js +54 -0
  25. package/dist/runtime/use-funnel-flow-controller.d.ts +58 -5
  26. package/dist/runtime/use-funnel-flow-controller.js +804 -122
  27. package/dist/runtime/use-step-choices.d.ts +58 -0
  28. package/dist/runtime/use-step-choices.js +187 -0
  29. package/dist/sdk/userAnswers.d.ts +1 -0
  30. package/dist/services/api.service.d.ts +10 -0
  31. package/dist/services/api.service.js +32 -0
  32. package/dist/services/funnel-sdk.service.d.ts +27 -0
  33. package/dist/services/funnel-sdk.service.js +18 -0
  34. package/dist/steps/choice-contract.d.ts +33 -0
  35. package/dist/steps/choice-contract.js +302 -0
  36. package/dist/steps/step-contract.d.ts +498 -0
  37. package/dist/steps/step-contract.js +506 -0
  38. package/dist/steps/types.d.ts +7 -5
  39. package/dist/steps/types.js +1 -18
  40. package/dist/testing/funnel-contract-journey-driver.d.ts +35 -0
  41. package/dist/testing/funnel-contract-journey-driver.js +266 -0
  42. package/dist/testing/index.d.ts +1 -0
  43. package/dist/testing/index.js +1 -0
  44. package/dist/validation/fixtures/invalid-reserved-identities.d.ts +249 -0
  45. package/dist/validation/fixtures/invalid-reserved-identities.js +20 -0
  46. package/dist/validation/fixtures/valid-v2-project.d.ts +419 -0
  47. package/dist/validation/fixtures/valid-v2-project.js +228 -0
  48. package/dist/validation/fixtures/valid-v3-project.d.ts +419 -0
  49. package/dist/validation/fixtures/valid-v3-project.js +30 -0
  50. package/dist/validation/funnel-contract-diagnostics.d.ts +49 -0
  51. package/dist/validation/funnel-contract-diagnostics.js +67 -0
  52. package/dist/validation/funnel-contract-validator.d.ts +30 -0
  53. package/dist/validation/funnel-contract-validator.js +1458 -0
  54. package/dist/validation/funnel-manifest-input.normalization.d.ts +67 -0
  55. package/dist/validation/funnel-manifest-input.normalization.js +386 -0
  56. package/package.json +9 -1
@@ -0,0 +1,58 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type FunnelChoiceCompletion } from '../steps/choice-contract.js';
3
+ import { type FunnelChoiceOption, type FunnelEmojiChoiceOption } from '../steps/step-contract.js';
4
+ import type { FunnelStepMeta } from '../steps/types.js';
5
+ import { type FunnelContractDiagnostic } from '../validation/funnel-contract-diagnostics.js';
6
+ type ChoiceOptions = readonly (FunnelChoiceOption | FunnelEmojiChoiceOption)[];
7
+ type ChoiceResult = FunnelChoiceCompletion | FunnelContractDiagnostic;
8
+ type ChoiceCommit = (stepMeta: FunnelStepMeta, result: ChoiceResult) => void;
9
+ export type FunnelStepChoices = {
10
+ selectSingle: (optionId: string) => void;
11
+ completeMulti: (optionIds: readonly string[]) => void;
12
+ };
13
+ export declare const createStepChoiceActions: (stepMeta: FunnelStepMeta, options: ChoiceOptions, commit: ChoiceCommit) => FunnelStepChoices;
14
+ export declare const registerInternalChoiceCommit: (carrier: object, commit: ChoiceCommit) => void;
15
+ export declare const FunnelChoiceCommitProvider: ({ carrier, children, }: {
16
+ carrier: object;
17
+ children: ReactNode;
18
+ }) => import("react").FunctionComponentElement<import("react").ProviderProps<ChoiceCommit | null>>;
19
+ export declare const useStepChoices: (stepMeta: FunnelStepMeta, options: ChoiceOptions) => FunnelStepChoices;
20
+ export type AtomicChoiceCommitResult = 'committed' | 'ignored' | 'quarantined' | 'record-failed';
21
+ export declare const consumeChoiceCompletionMarker: (marker: string | null, previousStepId: string) => {
22
+ consumed: boolean;
23
+ nextMarker: string | null;
24
+ };
25
+ type AtomicChoiceCommitInput<StepId extends string> = {
26
+ activeStepId: StepId;
27
+ activeStepMeta: FunnelStepMeta | undefined;
28
+ stepMeta: FunnelStepMeta;
29
+ result: ChoiceResult;
30
+ environment: 'development' | 'production' | 'test';
31
+ getAttributes: () => Record<string, unknown>;
32
+ writeAttributes: (attributes: Record<string, unknown>) => void;
33
+ isInFlight: () => boolean;
34
+ beginCommit: () => void;
35
+ cancelCommit: () => void;
36
+ recordCompletion: (input: {
37
+ selected?: Record<string, unknown>;
38
+ metadata?: Record<string, unknown>;
39
+ }) => boolean;
40
+ markCompletedBeforeNavigation: () => void;
41
+ clearCompletedBeforeNavigation: (markerWasInstalled: boolean) => void;
42
+ resolveNext: (attributes: Record<string, unknown>) => StepId;
43
+ navigate: (stepId: StepId) => void;
44
+ reportDiagnostic: (diagnostic: FunnelContractDiagnostic) => void;
45
+ };
46
+ export declare const runAtomicChoiceCommit: <StepId extends string>(input: AtomicChoiceCommitInput<StepId>) => AtomicChoiceCommitResult;
47
+ type ChoiceBypassOperation = 'setAnswer' | 'setAttribute' | 'goChoice' | 'completeStep';
48
+ export declare const enforceChoiceBypassGuard: (input: {
49
+ operation: ChoiceBypassOperation;
50
+ activeStepId: string;
51
+ sourceStepId?: string;
52
+ key?: string;
53
+ stepById: Record<string, FunnelStepMeta>;
54
+ stepContractVersion: number;
55
+ environment: "development" | "production" | "test";
56
+ reportDiagnostic: (diagnostic: FunnelContractDiagnostic) => void;
57
+ }) => boolean;
58
+ export {};
@@ -0,0 +1,187 @@
1
+ 'use client';
2
+ import { createContext, createElement, useContext, useMemo, } from 'react';
3
+ import { buildChoiceCompletion, parseFunnelChoiceDescriptor, } from '../steps/choice-contract.js';
4
+ import { LEGACY_UNVERSIONED_STEP_CONTRACT_VERSION, SUPPORTED_STEP_CONTRACT_VERSIONS, } from '../steps/step-contract.js';
5
+ import { createFunnelContractDiagnostic, } from '../validation/funnel-contract-diagnostics.js';
6
+ export const createStepChoiceActions = (stepMeta, options, commit) => ({
7
+ selectSingle: (optionId) => commit(stepMeta, buildChoiceCompletion(stepMeta, options, optionId)),
8
+ completeMulti: (optionIds) => commit(stepMeta, buildChoiceCompletion(stepMeta, options, optionIds)),
9
+ });
10
+ const FunnelChoiceCommitContext = createContext(null);
11
+ const internalChoiceCommits = new WeakMap();
12
+ export const registerInternalChoiceCommit = (carrier, commit) => {
13
+ internalChoiceCommits.set(carrier, commit);
14
+ };
15
+ export const FunnelChoiceCommitProvider = ({ carrier, children, }) => {
16
+ var _a;
17
+ return createElement(FunnelChoiceCommitContext.Provider, { value: (_a = internalChoiceCommits.get(carrier)) !== null && _a !== void 0 ? _a : null }, children);
18
+ };
19
+ export const useStepChoices = (stepMeta, options) => {
20
+ const commit = useContext(FunnelChoiceCommitContext);
21
+ if (!commit) {
22
+ throw new Error('useStepChoices must be used inside FunnelProvider');
23
+ }
24
+ return useMemo(() => createStepChoiceActions(stepMeta, options, commit), [commit, options, stepMeta]);
25
+ };
26
+ const isChoiceCompletion = (value) => {
27
+ try {
28
+ return typeof value === 'object'
29
+ && value !== null
30
+ && typeof value.answerKey === 'string'
31
+ && 'selected' in value
32
+ && 'value' in value;
33
+ }
34
+ catch (_a) {
35
+ return false;
36
+ }
37
+ };
38
+ const choiceIdentityMatches = (candidate, active) => {
39
+ if (!active) {
40
+ return false;
41
+ }
42
+ try {
43
+ const candidateDescriptor = parseFunnelChoiceDescriptor(candidate.type, candidate.choice);
44
+ const activeDescriptor = parseFunnelChoiceDescriptor(active.type, active.choice);
45
+ return candidate.id === active.id
46
+ && candidate.type === active.type
47
+ && candidateDescriptor.ok
48
+ && activeDescriptor.ok
49
+ && candidateDescriptor.descriptor.answerKey === activeDescriptor.descriptor.answerKey
50
+ && candidateDescriptor.descriptor.allowEmpty === activeDescriptor.descriptor.allowEmpty;
51
+ }
52
+ catch (_a) {
53
+ return false;
54
+ }
55
+ };
56
+ export const consumeChoiceCompletionMarker = (marker, previousStepId) => marker === previousStepId
57
+ ? { consumed: true, nextMarker: null }
58
+ : { consumed: false, nextMarker: marker };
59
+ export const runAtomicChoiceCommit = (input) => {
60
+ if (input.isInFlight()) {
61
+ return 'ignored';
62
+ }
63
+ const completion = isChoiceCompletion(input.result) ? input.result : null;
64
+ if (completion === null) {
65
+ const diagnostic = input.result;
66
+ if (diagnostic.stepId !== input.activeStepId) {
67
+ input.reportDiagnostic(diagnostic);
68
+ return 'ignored';
69
+ }
70
+ if (input.environment !== 'production') {
71
+ throw new Error(`${diagnostic.code}: ${diagnostic.reason}`);
72
+ }
73
+ input.beginCommit();
74
+ const currentAttributes = input.getAttributes();
75
+ let abortStarted = false;
76
+ let markerInstallationStarted = false;
77
+ const abortCommit = () => {
78
+ if (abortStarted) {
79
+ return;
80
+ }
81
+ abortStarted = true;
82
+ try {
83
+ input.clearCompletedBeforeNavigation(markerInstallationStarted);
84
+ }
85
+ finally {
86
+ input.cancelCommit();
87
+ }
88
+ };
89
+ try {
90
+ input.reportDiagnostic(diagnostic);
91
+ const nextStepId = input.resolveNext(currentAttributes);
92
+ if (!input.recordCompletion({})) {
93
+ abortCommit();
94
+ return 'record-failed';
95
+ }
96
+ markerInstallationStarted = true;
97
+ input.markCompletedBeforeNavigation();
98
+ input.navigate(nextStepId);
99
+ return 'quarantined';
100
+ }
101
+ catch (error) {
102
+ abortCommit();
103
+ throw error;
104
+ }
105
+ }
106
+ if (input.activeStepId !== input.stepMeta.id
107
+ || !choiceIdentityMatches(input.stepMeta, input.activeStepMeta)) {
108
+ return 'ignored';
109
+ }
110
+ input.beginCommit();
111
+ const currentAttributes = input.getAttributes();
112
+ let abortStarted = false;
113
+ let attributesWritten = false;
114
+ let markerInstallationStarted = false;
115
+ const abortCommit = () => {
116
+ if (abortStarted) {
117
+ return;
118
+ }
119
+ abortStarted = true;
120
+ try {
121
+ if (attributesWritten) {
122
+ input.writeAttributes(currentAttributes);
123
+ }
124
+ }
125
+ finally {
126
+ try {
127
+ input.clearCompletedBeforeNavigation(markerInstallationStarted);
128
+ }
129
+ finally {
130
+ input.cancelCommit();
131
+ }
132
+ }
133
+ };
134
+ try {
135
+ const nextAttributes = Object.assign(Object.assign({}, currentAttributes), { [completion.answerKey]: completion.value });
136
+ const nextStepId = input.resolveNext(nextAttributes);
137
+ attributesWritten = true;
138
+ input.writeAttributes(nextAttributes);
139
+ if (!input.recordCompletion({
140
+ selected: completion.selected,
141
+ metadata: { choice_answer_key: completion.answerKey },
142
+ })) {
143
+ abortCommit();
144
+ return 'record-failed';
145
+ }
146
+ markerInstallationStarted = true;
147
+ input.markCompletedBeforeNavigation();
148
+ input.navigate(nextStepId);
149
+ return 'committed';
150
+ }
151
+ catch (error) {
152
+ abortCommit();
153
+ throw error;
154
+ }
155
+ };
156
+ export const enforceChoiceBypassGuard = (input) => {
157
+ var _a;
158
+ const enforcesTypedChoices = SUPPORTED_STEP_CONTRACT_VERSIONS.some((version) => (version !== LEGACY_UNVERSIONED_STEP_CONTRACT_VERSION
159
+ && version === input.stepContractVersion));
160
+ if (!enforcesTypedChoices) {
161
+ return true;
162
+ }
163
+ const sourceStepId = (_a = input.sourceStepId) !== null && _a !== void 0 ? _a : input.activeStepId;
164
+ const sourceMeta = input.stepById[sourceStepId];
165
+ const activeMeta = input.stepById[input.activeStepId];
166
+ const shouldReject = input.operation === 'setAnswer' || input.operation === 'setAttribute'
167
+ ? Boolean((activeMeta === null || activeMeta === void 0 ? void 0 : activeMeta.choice) && input.key === activeMeta.choice.answerKey)
168
+ : Boolean(sourceMeta === null || sourceMeta === void 0 ? void 0 : sourceMeta.choice);
169
+ if (!shouldReject) {
170
+ return true;
171
+ }
172
+ const diagnostic = createFunnelContractDiagnostic({
173
+ code: 'FG-CHOICE-006',
174
+ file: null,
175
+ stepId: sourceStepId,
176
+ expected: 'useStepChoices',
177
+ received: { operation: input.operation },
178
+ reason: 'A generic answer or completion API attempted to bypass the typed choice transaction.',
179
+ guide: 'docs/product-specs/funnel-template-step-authoring.md',
180
+ repair: 'Complete descriptor-bearing choice steps with useStepChoices.',
181
+ });
182
+ if (input.environment !== 'production') {
183
+ throw new Error(`${diagnostic.code}: ${diagnostic.reason}`);
184
+ }
185
+ input.reportDiagnostic(diagnostic);
186
+ return false;
187
+ };
@@ -1,4 +1,5 @@
1
1
  import type { FunnelStepId } from '../runtime/funnel-runtime.js';
2
+ export type FunnelAnswerKey = string;
2
3
  export type FunnelBinaryChoice = 'yes' | 'no';
3
4
  export type FunnelBirthDate = {
4
5
  year: number;
@@ -51,6 +51,11 @@ export type TempPhotoUploadResult = {
51
51
  sizeBytes: number;
52
52
  uploadedAt: string;
53
53
  };
54
+ export type EmailCaptureResult = {
55
+ user: AppUser;
56
+ eventId: string;
57
+ eventCreated: boolean;
58
+ };
54
59
  declare class ApiService {
55
60
  private readFileAsDataUrl;
56
61
  getOrCreateClientUserId(): string;
@@ -68,6 +73,11 @@ declare class ApiService {
68
73
  updateUser(user: AppUser, options?: {
69
74
  attribution?: FunnelUserAttribution;
70
75
  }): Promise<AppUser>;
76
+ captureEmail(input: {
77
+ userId: string;
78
+ email: string;
79
+ environment: 'test' | 'live';
80
+ }): Promise<EmailCaptureResult>;
71
81
  syncUrlUserAttributes(input: {
72
82
  user: AppUserInput;
73
83
  attributes?: FunnelUserAnswers;
@@ -286,6 +286,38 @@ class ApiService {
286
286
  fallbackDocument: withMergedAttributionDocument(user.document, options === null || options === void 0 ? void 0 : options.attribution),
287
287
  });
288
288
  }
289
+ async captureEmail(input) {
290
+ var _a, _b;
291
+ const userId = input.userId.trim();
292
+ const email = input.email.trim().toLowerCase();
293
+ const payload = await funnelSdkService.captureEmail({
294
+ userId,
295
+ email,
296
+ environment: input.environment,
297
+ });
298
+ const eventId = asString(payload.eventId);
299
+ const responseUser = isRecord(payload.user) ? payload.user : null;
300
+ const responseUserId = responseUser === null || responseUser === void 0 ? void 0 : responseUser.user_id;
301
+ const responseEmail = (_b = (_a = asString(responseUser === null || responseUser === void 0 ? void 0 : responseUser.email)) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : null;
302
+ if (!eventId
303
+ || typeof payload.eventCreated !== 'boolean'
304
+ || !responseUser
305
+ || typeof responseUserId !== 'string'
306
+ || responseUserId !== userId
307
+ || responseEmail !== email) {
308
+ throw new Error('Invalid email capture response');
309
+ }
310
+ const persistedUserId = persistUserId(userId, FUNNEL_ID);
311
+ return {
312
+ user: toAppUser({
313
+ apiUser: Object.assign(Object.assign({}, responseUser), { user_id: userId, email }),
314
+ userId: persistedUserId,
315
+ fallbackEmail: email,
316
+ }),
317
+ eventId,
318
+ eventCreated: payload.eventCreated,
319
+ };
320
+ }
289
321
  async syncUrlUserAttributes(input) {
290
322
  var _a;
291
323
  if (!hasUrlUserProfileAttributes(input.urlAttributes)) {
@@ -20,6 +20,22 @@ export type FunnelSdkBootstrapUserInput = {
20
20
  attribution?: FunnelSdkJsonObject | null;
21
21
  runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
22
22
  };
23
+ export type FunnelSdkCaptureEmailInput = {
24
+ userId: string;
25
+ email: string;
26
+ environment: FunnelSdkRuntimeMode;
27
+ runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
28
+ };
29
+ export type FunnelSdkCaptureEmailResponse = {
30
+ user: {
31
+ user_id?: unknown;
32
+ fullName?: unknown;
33
+ email?: unknown;
34
+ document?: unknown;
35
+ };
36
+ eventId: string;
37
+ eventCreated: boolean;
38
+ };
23
39
  export type FunnelSdkGetCurrentUserInput = {
24
40
  userId: string;
25
41
  runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
@@ -82,6 +98,11 @@ export type FunnelSdkCreateSubscriptionCheckoutInput = {
82
98
  analyticsMetadata?: Record<string, unknown> | null;
83
99
  billingInterval?: string | null;
84
100
  billingIntervalCount?: number | null;
101
+ followUpProviderPlanId?: string | null;
102
+ followUpPlanTitle?: string | null;
103
+ followUpBillingInterval?: string | null;
104
+ followUpBillingIntervalCount?: number | null;
105
+ introIterations?: number | null;
85
106
  couponId?: string | null;
86
107
  customerEmail?: string | null;
87
108
  description?: string | null;
@@ -125,6 +146,11 @@ export type FunnelSdkUpdateSubscriptionCheckoutPlanInput = {
125
146
  analyticsMetadata?: Record<string, unknown> | null;
126
147
  billingInterval?: string | null;
127
148
  billingIntervalCount?: number | null;
149
+ followUpProviderPlanId?: string | null;
150
+ followUpPlanTitle?: string | null;
151
+ followUpBillingInterval?: string | null;
152
+ followUpBillingIntervalCount?: number | null;
153
+ introIterations?: number | null;
128
154
  couponId?: string | null;
129
155
  customerEmail?: string | null;
130
156
  description?: string | null;
@@ -211,6 +237,7 @@ declare class FunnelSdkService {
211
237
  private getJson;
212
238
  private postJson;
213
239
  bootstrapUser<ResponsePayload = FunnelSdkJsonObject>(input?: FunnelSdkBootstrapUserInput): Promise<ResponsePayload>;
240
+ captureEmail(input: FunnelSdkCaptureEmailInput): Promise<FunnelSdkCaptureEmailResponse>;
214
241
  getCurrentUser<ResponsePayload = FunnelSdkJsonObject>(input: FunnelSdkGetCurrentUserInput): Promise<ResponsePayload>;
215
242
  getUserSubscriptionStatus<ResponsePayload = FunnelSdkJsonObject>(input?: FunnelSdkGetUserSubscriptionStatusInput): Promise<ResponsePayload>;
216
243
  claimUserSubscription<ResponsePayload = FunnelSdkJsonObject>(input: FunnelSdkClaimUserSubscriptionInput): Promise<ResponsePayload>;
@@ -1,5 +1,6 @@
1
1
  import { buildMainApiUrl, FUNNEL_ID, FUNNEL_SDK_PUBLISHABLE_KEY, FUNNEL_VERSION_ID, } from './runtime-api.config.js';
2
2
  import { resolvePreviewPaymentPublishableKey } from './public-env.js';
3
+ import { CURRENT_STEP_CONTRACT_VERSION } from '../steps/step-contract.js';
3
4
  const trimTrailingSlash = (value) => value.replace(/\/+$/, '');
4
5
  const toNormalizedPath = (path) => (path.startsWith('/') ? path : `/${path}`);
5
6
  const asTrimmedStringOrNull = (value) => {
@@ -113,6 +114,18 @@ class FunnelSdkService {
113
114
  errorMessage: 'Failed to bootstrap user session',
114
115
  });
115
116
  }
117
+ async captureEmail(input) {
118
+ return this.postJson('/sdk/public/users/email-capture', {
119
+ runtimeConfig: input.runtimeConfig,
120
+ body: {
121
+ user_id: input.userId,
122
+ email: input.email,
123
+ environment: input.environment,
124
+ stepContractVersion: CURRENT_STEP_CONTRACT_VERSION,
125
+ },
126
+ errorMessage: 'Failed to capture email',
127
+ });
128
+ }
116
129
  async getCurrentUser(input) {
117
130
  const { funnelId } = resolveRuntimeConfig(input.runtimeConfig);
118
131
  return this.getJson('/sdk/public/users/me', {
@@ -216,6 +229,11 @@ class FunnelSdkService {
216
229
  amountCents: input.amountCents,
217
230
  billingInterval: input.billingInterval,
218
231
  billingIntervalCount: input.billingIntervalCount,
232
+ followUpProviderPlanId: input.followUpProviderPlanId,
233
+ followUpPlanTitle: input.followUpPlanTitle,
234
+ followUpBillingInterval: input.followUpBillingInterval,
235
+ followUpBillingIntervalCount: input.followUpBillingIntervalCount,
236
+ introIterations: input.introIterations,
219
237
  couponId: input.couponId,
220
238
  analyticsMetadata: input.analyticsMetadata,
221
239
  customerEmail: input.customerEmail,
@@ -0,0 +1,33 @@
1
+ import type { FunnelAnswerKey } from '../sdk/userAnswers.js';
2
+ import { type FunnelContractDiagnostic } from '../validation/funnel-contract-diagnostics.js';
3
+ import { FUNNEL_STEP_CONTRACTS, type FunnelChoiceDescriptor, type FunnelChoiceOption, type FunnelEmojiChoiceOption, type FunnelStepChoiceContract, type FunnelStepType } from './step-contract.js';
4
+ import type { FunnelStepMeta } from './types.js';
5
+ export type FunnelChoiceStepType = {
6
+ [Type in FunnelStepType]: (typeof FUNNEL_STEP_CONTRACTS)[Type] extends {
7
+ readonly choice: FunnelStepChoiceContract;
8
+ } ? Type : never;
9
+ }[FunnelStepType];
10
+ export declare const FUNNEL_CHOICE_STEP_TYPES: readonly FunnelChoiceStepType[];
11
+ export type FunnelChoiceValue = string | string[];
12
+ export type FunnelChoiceCompletion = {
13
+ answerKey: FunnelAnswerKey;
14
+ value: FunnelChoiceValue;
15
+ selected: Record<string, FunnelChoiceValue>;
16
+ };
17
+ type FunnelChoiceStepMeta = Pick<FunnelStepMeta, 'id' | 'type' | 'choice'>;
18
+ type FunnelChoiceOptions = readonly (FunnelChoiceOption | FunnelEmojiChoiceOption)[];
19
+ export declare const MAX_FUNNEL_CHOICE_ITEMS = 4096;
20
+ export declare const MAX_FUNNEL_CHOICE_IDENTIFIER_LENGTH = 256;
21
+ export declare const MAX_FUNNEL_CHOICE_CONTENT_STRING_LENGTH = 4096;
22
+ export type FunnelChoiceDescriptorParseResult = {
23
+ ok: true;
24
+ descriptor: FunnelChoiceDescriptor;
25
+ contract: FunnelStepChoiceContract;
26
+ stepType: FunnelChoiceStepType;
27
+ } | {
28
+ ok: false;
29
+ issue: 'not-choice-step-type' | 'invalid-descriptor-shape' | 'invalid-answer-key' | 'invalid-allow-empty';
30
+ };
31
+ export declare const parseFunnelChoiceDescriptor: (stepType: unknown, choice: unknown) => FunnelChoiceDescriptorParseResult;
32
+ export declare const buildChoiceCompletion: (stepMeta: FunnelChoiceStepMeta, options: FunnelChoiceOptions, ids: string | readonly string[]) => FunnelChoiceCompletion | FunnelContractDiagnostic;
33
+ export {};