@richardmcquiston01/chat-and-react-core 0.5.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.
@@ -0,0 +1,238 @@
1
+ type AnswerMap = Record<string, string | string[]>;
2
+ type ChatInputType = 'text' | 'textarea' | 'dropdown' | 'radio' | 'checkbox';
3
+ type JsonLogicRule = Record<string, unknown>;
4
+ interface ChatConditionRef {
5
+ ref: string;
6
+ }
7
+ interface ChatQuestion {
8
+ id: string;
9
+ type: 'string' | 'array';
10
+ 'x-chat-input': ChatInputType;
11
+ title: string;
12
+ 'x-chat-placeholder'?: string;
13
+ 'x-chat-condition'?: JsonLogicRule | ChatConditionRef;
14
+ enum?: string[];
15
+ items?: {
16
+ enum: string[];
17
+ };
18
+ }
19
+ interface BranchingAlways {
20
+ type: 'always';
21
+ destination_id: string;
22
+ }
23
+ interface BranchingIf {
24
+ type: 'if';
25
+ form_element: string;
26
+ value: string;
27
+ destination_id: string;
28
+ }
29
+ type BranchingRule = BranchingAlways | BranchingIf;
30
+ interface ChatPage {
31
+ id: string;
32
+ title: string;
33
+ questions: ChatQuestion[];
34
+ branching: BranchingRule[];
35
+ }
36
+ interface ChatOptions {
37
+ userResumable: boolean;
38
+ localStorage: boolean;
39
+ autoComplete: boolean;
40
+ }
41
+ interface ChatSchema {
42
+ '$schema'?: string;
43
+ 'x-chat-version': '1';
44
+ 'x-chat-options'?: Partial<ChatOptions>;
45
+ 'x-chat-pages': ChatPage[];
46
+ 'x-chat-rules'?: Record<string, JsonLogicRule>;
47
+ }
48
+ interface PageSubmitEvent {
49
+ type: 'page:submit';
50
+ sessionId: string;
51
+ pageId: string;
52
+ visitIndex: number;
53
+ answers: AnswerMap;
54
+ }
55
+ interface FormCompleteEvent {
56
+ type: 'form:complete';
57
+ sessionId: string;
58
+ allAnswers: AnswerMap;
59
+ }
60
+ type ChatEvent = PageSubmitEvent | FormCompleteEvent;
61
+ type ChatErrorCode = 'SCHEMA_INVALID' | 'RULE_EVAL_FAILED' | 'ADAPTER_ERROR' | 'STORAGE_UNAVAILABLE';
62
+ interface ChatError {
63
+ code: ChatErrorCode;
64
+ message: string;
65
+ cause?: unknown;
66
+ }
67
+ interface InputAdapter {
68
+ transform(raw: unknown): unknown;
69
+ }
70
+ interface OutputAdapter {
71
+ emit(event: ChatEvent): void;
72
+ }
73
+ interface ErrorLogAdapter {
74
+ log(error: ChatError): void;
75
+ }
76
+ interface ChatControllerConfig {
77
+ schema: unknown;
78
+ inputAdapter?: InputAdapter;
79
+ outputAdapter?: OutputAdapter;
80
+ errorAdapter?: ErrorLogAdapter;
81
+ }
82
+
83
+ /**
84
+ * Parses and validates an unknown input as a {@link ChatSchema}.
85
+ *
86
+ * @param raw - The raw input to validate.
87
+ * @returns The typed {@link ChatSchema} if valid.
88
+ * @throws `{ code: 'SCHEMA_INVALID', message: string }` on any validation failure.
89
+ */
90
+ declare function parseSchema(raw: unknown): ChatSchema;
91
+
92
+ /**
93
+ * Evaluates JSON Logic rules against provided answers.
94
+ * Wraps json-logic-js to provide type-safe rule evaluation.
95
+ */
96
+ declare class RuleEngine {
97
+ /**
98
+ * Evaluates a JSON Logic rule against an answer map.
99
+ * @param rule The JSON Logic rule to evaluate
100
+ * @param answers The answer map to evaluate the rule against
101
+ * @returns The boolean result of the rule evaluation
102
+ * @throws {{ code: 'RULE_EVAL_FAILED', message: string, cause: unknown }} if rule evaluation fails
103
+ */
104
+ evaluate(rule: JsonLogicRule, answers: AnswerMap): boolean;
105
+ }
106
+
107
+ interface StorageBackend {
108
+ getItem(key: string): string | null;
109
+ setItem(key: string, value: string): void;
110
+ removeItem(key: string): void;
111
+ key(index: number): string | null;
112
+ readonly length: number;
113
+ }
114
+ interface SessionProgress {
115
+ currentPageId: string;
116
+ answers: AnswerMap;
117
+ }
118
+ declare class SessionManager {
119
+ private readonly storage;
120
+ private readonly sessionId;
121
+ private readonly available;
122
+ constructor(storage?: StorageBackend);
123
+ getSessionId(): string;
124
+ get isStorageAvailable(): boolean;
125
+ saveProgress(currentPageId: string, answers: AnswerMap): void;
126
+ loadProgress(): SessionProgress | null;
127
+ saveInput(questionId: string, value: string | string[]): void;
128
+ loadInput(questionId: string): string | string[] | null;
129
+ clearAll(): void;
130
+ private loadOrCreateSessionId;
131
+ private checkStorage;
132
+ }
133
+
134
+ type EventName = 'page:submit' | 'form:complete';
135
+ type Listener = (event: ChatEvent) => void;
136
+ /**
137
+ * Central orchestrator for the chat-and-react form flow.
138
+ * Drives page navigation, branching, session persistence, and event emission.
139
+ */
140
+ declare class ChatController {
141
+ private schema;
142
+ private options;
143
+ private readonly config;
144
+ private readonly session;
145
+ private readonly ruleEngine;
146
+ private currentPage;
147
+ private allAnswers;
148
+ private visitIndex;
149
+ private readonly listeners;
150
+ /**
151
+ * @param config - Controller configuration including schema and optional adapters.
152
+ * @param sessionManager - Optional session manager for test injection.
153
+ */
154
+ constructor(config: ChatControllerConfig, sessionManager?: SessionManager);
155
+ /**
156
+ * Initialises the controller: parses the schema, resolves options,
157
+ * restores any saved session, and positions to the first page.
158
+ */
159
+ start(): void;
160
+ /**
161
+ * Returns the page currently being presented to the user, or null if complete.
162
+ */
163
+ getCurrentPage(): ChatPage | null;
164
+ /**
165
+ * Returns the subset of questions on the current page that pass their
166
+ * `x-chat-condition` rule (if any). Questions without a condition are always visible.
167
+ */
168
+ getVisibleQuestions(): ChatQuestion[];
169
+ /**
170
+ * Returns the resolved options for the current session (schema options merged with defaults).
171
+ */
172
+ getOptions(): ChatOptions;
173
+ /**
174
+ * Submits the answers for the current page and advances the session.
175
+ * - Accumulates answers into `allAnswers`.
176
+ * - Emits `page:submit` (suppressed when `userResumable` is false).
177
+ * - Resolves branching to find the next page.
178
+ * - Emits `form:complete` and clears session when `**FORM_COMPLETED**` is reached.
179
+ *
180
+ * @param pageAnswers - Answers collected for the current page.
181
+ */
182
+ submitPage(pageAnswers: AnswerMap): void;
183
+ /**
184
+ * Registers an event listener for the given event type.
185
+ *
186
+ * @param event - The event to listen for.
187
+ * @param listener - The callback to invoke when the event fires.
188
+ */
189
+ on(event: EventName, listener: Listener): void;
190
+ /**
191
+ * Removes a previously registered event listener.
192
+ *
193
+ * @param event - The event type to deregister from.
194
+ * @param listener - The specific listener to remove.
195
+ */
196
+ off(event: EventName, listener: Listener): void;
197
+ /**
198
+ * Tears down the controller: removes all listeners and nulls internal state.
199
+ */
200
+ destroy(): void;
201
+ /**
202
+ * Evaluates whether a question should be shown based on its `x-chat-condition`.
203
+ * Returns true when no condition is present. On evaluation failure, logs and returns false.
204
+ */
205
+ private isQuestionVisible;
206
+ /**
207
+ * Walks the branching rules top-to-bottom. Returns the first match's destination_id.
208
+ * Falls back to `**FORM_COMPLETED**` if nothing matches (should not happen with valid schemas).
209
+ */
210
+ private resolveBranching;
211
+ /**
212
+ * Calls `outputAdapter.emit()` and swallows adapter errors (logging them instead).
213
+ */
214
+ private safeEmit;
215
+ /**
216
+ * Notifies all registered listeners for the given event.
217
+ */
218
+ private emit;
219
+ /**
220
+ * Logs the error via the error adapter and then throws it.
221
+ * Used for unrecoverable errors (schema invalid, adapter failure on start).
222
+ */
223
+ private handleFatal;
224
+ }
225
+
226
+ declare class IdentityInputAdapter implements InputAdapter {
227
+ transform(raw: unknown): unknown;
228
+ }
229
+
230
+ declare class BrowserEventAdapter implements OutputAdapter {
231
+ emit(event: ChatEvent): void;
232
+ }
233
+
234
+ declare class ConsoleErrorAdapter implements ErrorLogAdapter {
235
+ log(error: ChatError): void;
236
+ }
237
+
238
+ export { type AnswerMap, type BranchingAlways, type BranchingIf, type BranchingRule, BrowserEventAdapter, type ChatConditionRef, ChatController, type ChatControllerConfig, type ChatError, type ChatErrorCode, type ChatEvent, type ChatInputType, type ChatOptions, type ChatPage, type ChatQuestion, type ChatSchema, ConsoleErrorAdapter, type ErrorLogAdapter, type FormCompleteEvent, IdentityInputAdapter, type InputAdapter, type JsonLogicRule, type OutputAdapter, type PageSubmitEvent, RuleEngine, SessionManager, type SessionProgress, type StorageBackend, parseSchema };
@@ -0,0 +1,238 @@
1
+ type AnswerMap = Record<string, string | string[]>;
2
+ type ChatInputType = 'text' | 'textarea' | 'dropdown' | 'radio' | 'checkbox';
3
+ type JsonLogicRule = Record<string, unknown>;
4
+ interface ChatConditionRef {
5
+ ref: string;
6
+ }
7
+ interface ChatQuestion {
8
+ id: string;
9
+ type: 'string' | 'array';
10
+ 'x-chat-input': ChatInputType;
11
+ title: string;
12
+ 'x-chat-placeholder'?: string;
13
+ 'x-chat-condition'?: JsonLogicRule | ChatConditionRef;
14
+ enum?: string[];
15
+ items?: {
16
+ enum: string[];
17
+ };
18
+ }
19
+ interface BranchingAlways {
20
+ type: 'always';
21
+ destination_id: string;
22
+ }
23
+ interface BranchingIf {
24
+ type: 'if';
25
+ form_element: string;
26
+ value: string;
27
+ destination_id: string;
28
+ }
29
+ type BranchingRule = BranchingAlways | BranchingIf;
30
+ interface ChatPage {
31
+ id: string;
32
+ title: string;
33
+ questions: ChatQuestion[];
34
+ branching: BranchingRule[];
35
+ }
36
+ interface ChatOptions {
37
+ userResumable: boolean;
38
+ localStorage: boolean;
39
+ autoComplete: boolean;
40
+ }
41
+ interface ChatSchema {
42
+ '$schema'?: string;
43
+ 'x-chat-version': '1';
44
+ 'x-chat-options'?: Partial<ChatOptions>;
45
+ 'x-chat-pages': ChatPage[];
46
+ 'x-chat-rules'?: Record<string, JsonLogicRule>;
47
+ }
48
+ interface PageSubmitEvent {
49
+ type: 'page:submit';
50
+ sessionId: string;
51
+ pageId: string;
52
+ visitIndex: number;
53
+ answers: AnswerMap;
54
+ }
55
+ interface FormCompleteEvent {
56
+ type: 'form:complete';
57
+ sessionId: string;
58
+ allAnswers: AnswerMap;
59
+ }
60
+ type ChatEvent = PageSubmitEvent | FormCompleteEvent;
61
+ type ChatErrorCode = 'SCHEMA_INVALID' | 'RULE_EVAL_FAILED' | 'ADAPTER_ERROR' | 'STORAGE_UNAVAILABLE';
62
+ interface ChatError {
63
+ code: ChatErrorCode;
64
+ message: string;
65
+ cause?: unknown;
66
+ }
67
+ interface InputAdapter {
68
+ transform(raw: unknown): unknown;
69
+ }
70
+ interface OutputAdapter {
71
+ emit(event: ChatEvent): void;
72
+ }
73
+ interface ErrorLogAdapter {
74
+ log(error: ChatError): void;
75
+ }
76
+ interface ChatControllerConfig {
77
+ schema: unknown;
78
+ inputAdapter?: InputAdapter;
79
+ outputAdapter?: OutputAdapter;
80
+ errorAdapter?: ErrorLogAdapter;
81
+ }
82
+
83
+ /**
84
+ * Parses and validates an unknown input as a {@link ChatSchema}.
85
+ *
86
+ * @param raw - The raw input to validate.
87
+ * @returns The typed {@link ChatSchema} if valid.
88
+ * @throws `{ code: 'SCHEMA_INVALID', message: string }` on any validation failure.
89
+ */
90
+ declare function parseSchema(raw: unknown): ChatSchema;
91
+
92
+ /**
93
+ * Evaluates JSON Logic rules against provided answers.
94
+ * Wraps json-logic-js to provide type-safe rule evaluation.
95
+ */
96
+ declare class RuleEngine {
97
+ /**
98
+ * Evaluates a JSON Logic rule against an answer map.
99
+ * @param rule The JSON Logic rule to evaluate
100
+ * @param answers The answer map to evaluate the rule against
101
+ * @returns The boolean result of the rule evaluation
102
+ * @throws {{ code: 'RULE_EVAL_FAILED', message: string, cause: unknown }} if rule evaluation fails
103
+ */
104
+ evaluate(rule: JsonLogicRule, answers: AnswerMap): boolean;
105
+ }
106
+
107
+ interface StorageBackend {
108
+ getItem(key: string): string | null;
109
+ setItem(key: string, value: string): void;
110
+ removeItem(key: string): void;
111
+ key(index: number): string | null;
112
+ readonly length: number;
113
+ }
114
+ interface SessionProgress {
115
+ currentPageId: string;
116
+ answers: AnswerMap;
117
+ }
118
+ declare class SessionManager {
119
+ private readonly storage;
120
+ private readonly sessionId;
121
+ private readonly available;
122
+ constructor(storage?: StorageBackend);
123
+ getSessionId(): string;
124
+ get isStorageAvailable(): boolean;
125
+ saveProgress(currentPageId: string, answers: AnswerMap): void;
126
+ loadProgress(): SessionProgress | null;
127
+ saveInput(questionId: string, value: string | string[]): void;
128
+ loadInput(questionId: string): string | string[] | null;
129
+ clearAll(): void;
130
+ private loadOrCreateSessionId;
131
+ private checkStorage;
132
+ }
133
+
134
+ type EventName = 'page:submit' | 'form:complete';
135
+ type Listener = (event: ChatEvent) => void;
136
+ /**
137
+ * Central orchestrator for the chat-and-react form flow.
138
+ * Drives page navigation, branching, session persistence, and event emission.
139
+ */
140
+ declare class ChatController {
141
+ private schema;
142
+ private options;
143
+ private readonly config;
144
+ private readonly session;
145
+ private readonly ruleEngine;
146
+ private currentPage;
147
+ private allAnswers;
148
+ private visitIndex;
149
+ private readonly listeners;
150
+ /**
151
+ * @param config - Controller configuration including schema and optional adapters.
152
+ * @param sessionManager - Optional session manager for test injection.
153
+ */
154
+ constructor(config: ChatControllerConfig, sessionManager?: SessionManager);
155
+ /**
156
+ * Initialises the controller: parses the schema, resolves options,
157
+ * restores any saved session, and positions to the first page.
158
+ */
159
+ start(): void;
160
+ /**
161
+ * Returns the page currently being presented to the user, or null if complete.
162
+ */
163
+ getCurrentPage(): ChatPage | null;
164
+ /**
165
+ * Returns the subset of questions on the current page that pass their
166
+ * `x-chat-condition` rule (if any). Questions without a condition are always visible.
167
+ */
168
+ getVisibleQuestions(): ChatQuestion[];
169
+ /**
170
+ * Returns the resolved options for the current session (schema options merged with defaults).
171
+ */
172
+ getOptions(): ChatOptions;
173
+ /**
174
+ * Submits the answers for the current page and advances the session.
175
+ * - Accumulates answers into `allAnswers`.
176
+ * - Emits `page:submit` (suppressed when `userResumable` is false).
177
+ * - Resolves branching to find the next page.
178
+ * - Emits `form:complete` and clears session when `**FORM_COMPLETED**` is reached.
179
+ *
180
+ * @param pageAnswers - Answers collected for the current page.
181
+ */
182
+ submitPage(pageAnswers: AnswerMap): void;
183
+ /**
184
+ * Registers an event listener for the given event type.
185
+ *
186
+ * @param event - The event to listen for.
187
+ * @param listener - The callback to invoke when the event fires.
188
+ */
189
+ on(event: EventName, listener: Listener): void;
190
+ /**
191
+ * Removes a previously registered event listener.
192
+ *
193
+ * @param event - The event type to deregister from.
194
+ * @param listener - The specific listener to remove.
195
+ */
196
+ off(event: EventName, listener: Listener): void;
197
+ /**
198
+ * Tears down the controller: removes all listeners and nulls internal state.
199
+ */
200
+ destroy(): void;
201
+ /**
202
+ * Evaluates whether a question should be shown based on its `x-chat-condition`.
203
+ * Returns true when no condition is present. On evaluation failure, logs and returns false.
204
+ */
205
+ private isQuestionVisible;
206
+ /**
207
+ * Walks the branching rules top-to-bottom. Returns the first match's destination_id.
208
+ * Falls back to `**FORM_COMPLETED**` if nothing matches (should not happen with valid schemas).
209
+ */
210
+ private resolveBranching;
211
+ /**
212
+ * Calls `outputAdapter.emit()` and swallows adapter errors (logging them instead).
213
+ */
214
+ private safeEmit;
215
+ /**
216
+ * Notifies all registered listeners for the given event.
217
+ */
218
+ private emit;
219
+ /**
220
+ * Logs the error via the error adapter and then throws it.
221
+ * Used for unrecoverable errors (schema invalid, adapter failure on start).
222
+ */
223
+ private handleFatal;
224
+ }
225
+
226
+ declare class IdentityInputAdapter implements InputAdapter {
227
+ transform(raw: unknown): unknown;
228
+ }
229
+
230
+ declare class BrowserEventAdapter implements OutputAdapter {
231
+ emit(event: ChatEvent): void;
232
+ }
233
+
234
+ declare class ConsoleErrorAdapter implements ErrorLogAdapter {
235
+ log(error: ChatError): void;
236
+ }
237
+
238
+ export { type AnswerMap, type BranchingAlways, type BranchingIf, type BranchingRule, BrowserEventAdapter, type ChatConditionRef, ChatController, type ChatControllerConfig, type ChatError, type ChatErrorCode, type ChatEvent, type ChatInputType, type ChatOptions, type ChatPage, type ChatQuestion, type ChatSchema, ConsoleErrorAdapter, type ErrorLogAdapter, type FormCompleteEvent, IdentityInputAdapter, type InputAdapter, type JsonLogicRule, type OutputAdapter, type PageSubmitEvent, RuleEngine, SessionManager, type SessionProgress, type StorageBackend, parseSchema };