@case-framework/survey-core 0.6.2 → 0.8.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.
package/build/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import { $ as generateId, A as DurationUnits, B as ConstExpression, C as ReservedSurveyItemTypes, D as deserializeSurveyItemPrefill, E as SurveyItemPrefillTargetType, F as TemplateDefTypes, G as FunctionExpression, H as ContextVariableType, I as deserializeTemplateValue, J as ReferenceUsageType, K as FunctionExpressionNames, L as deserializeTemplateValues, M as ValueType, N as assertResponseValue, O as prefillTargetsEqual, P as isResponseValue, Q as generateCodingKey, R as serializeTemplateValue, S as SurveyItemKey, T as SurveyItemPrefillApplyMode, U as Expression, V as ContextVariableExpression, W as ExpressionType, X as ValueReferenceMethod, Y as ValueReference, Z as createSeededRandom, _ as toItemTypeDefinitionRegistry, a as getContentPlainText, b as builtInItemCoreRegistry, c as hasRenderableRichTextContent, d as SurveyTranslations, et as shuffleArray, f as validateLocale, g as createItemTypeDefinitionRegistry, h as createItemCore, i as getAssetUsagesFromContent, j as NumberPrecision, k as serializeSurveyItemPrefill, l as isContentEmpty, m as createFullRegistry, n as ContentType, nt as structuredCloneMethod, o as getPlainTextFromRichTextContent, p as CURRENT_SURVEY_SCHEMA, q as ResponseVariableExpression, r as createRichTextContent, s as hasRenderableRichTextBlock, t as Survey, tt as shuffleIndices, u as SurveyItemTranslations, v as GroupItemCore, w as SurveyItemCore, x as isBuiltInItemType, y as PageBreakItemCore, z as serializeTemplateValues } from "./survey-BKblV5rN.mjs";
1
+ import { $ as const_date, $n as responseValuesEqual, $t as CtxPFlagDateEditor, A as createItemTypeDefinitionRegistry, An as deserializeTemplateValue, At as min, B as deserializeSurveyItemPrefill, Bn as FunctionExpression, Bt as str_list_contains, C as SurveyItemTranslations, Cn as findIntroducedBlockingExpressionReferenceDiagnostics, Ct as eq, D as CURRENT_SURVEY_SCHEMA_VERSION, Dn as responseValueReferenceIds, Dt as lt, E as CURRENT_SURVEY_SCHEMA, En as replaceResponseVariableReferences, Et as in_range, F as isBuiltInItemType, Fn as ConstExpression, Ft as response_number, G as generateId, Gn as ValueReferenceMethod, Gt as ConstDateEditor, H as serializeSurveyItemPrefill, Hn as ResponseVariableExpression, Ht as AndExpressionEditor, I as getCompositionLabel, In as ContextVariableExpression, It as response_number_array, J as shuffleIndices, Jn as allocateResponseVariableName, Jt as ConstStringArrayEditor, K as resolveUniqueItemKey, Kn as ResponseSlotRegistry, Kt as ConstNumberArrayEditor, L as SurveyItemCore, Ln as ContextVariableType, Lt as response_string, M as GroupItemCore, Mn as serializeTemplateValue, Mt as response_boolean, N as PageBreakItemCore, Nn as serializeTemplateValues, Nt as response_date, O as createFullRegistry, On as DATE_FUNCTION_SIGNATURES, Ot as lte, P as builtInItemCoreRegistry, Pn as ExpressionEvaluator, Pt as response_date_array, Q as const_boolean, Qn as responseValueElements, Qt as CtxLocaleEditor, R as SurveyItemPrefillApplyMode, Rn as Expression, Rt as response_string_array, S as isContentEmpty, Sn as expressionReferenceDiagnosticKey, St as date_min, T as validateLocale, Tn as isBlockingSurveyExpressionReferenceDiagnostic, Tt as gte, U as createSeededRandom, Un as ReferenceUsageType, Ut as ConstBooleanEditor, V as prefillTargetsEqual, Vn as FunctionExpressionNames, Vt as sum, W as generateCodingKey, Wn as ValueReference, Wt as ConstDateArrayEditor, X as ReservedSurveyItemTypes, Xn as suggestResponseVariableName, Xt as CtxCustomExpressionEditor, Y as structuredCloneMethod, Yn as isValidResponseVariableName, Yt as ConstStringEditor, Z as and, Zn as invalidResponseDomainValues, Zt as CtxCustomValueEditor, _ as getAssetUsagesFromContent, _n as SURVEY_EXPRESSION_REFERENCE_DIAGNOSTIC_POLICY, _r as differenceCalendarDates, _t as date_gt, a as resolvedSlotEncoding, an as GtExpressionEditor, ar as SurveyItemResponse, at as ctx_custom_expression, b as hasRenderableRichTextBlock, bn as collectExpressionResponseReferences, bt as date_lte, c as EXPORTER_PROFILE_SCHEMA_VERSION, cn as LtExpressionEditor, cr as ValueType, ct as ctx_pflag_date, d as SlotTransformMode, dn as MinExpressionEditor, dr as addCalendarDate, dt as ctx_pflag_string, en as CtxPFlagIsDefinedEditor, er as validateResponseSlotValue, et as const_date_array, f as decodeExportColumnId, fn as OrExpressionEditor, fr as calendarDateBounds, ft as date_add_days, g as createRichTextContent, gn as SumExpressionEditor, gr as compareCalendarDates, gt as date_eq, h as ContentType, hn as StrListContainsExpressionEditor, hr as calendarDateToLocalDate, ht as date_diff, i as projectSlot, in as ExpressionEditor, ir as ResponseItem, it as const_string_array, j as toItemTypeDefinitionRegistry, jn as deserializeTemplateValues, jt as or, k as createItemCore, kn as TemplateDefTypes, kt as max, l as ExportColumnSource, ln as LteExpressionEditor, lr as assertResponseValue, lt as ctx_pflag_is_defined, m as Survey, mn as StrEqExpressionEditor, mr as calendarDatePrecision, mt as date_add_years, n as getDefaultProjectionIssues, nn as CtxPFlagStringEditor, nr as SURVEY_RESPONSE_SCHEMA_VERSION, nt as const_number_array, o as DEFAULT_TRANSFORM, on as GteExpressionEditor, or as DurationUnits, ot as ctx_custom_value, p as encodeExportColumnId, pn as ResponseVariableEditor, pr as calendarDateFromLocalDate, pt as date_add_months, q as shuffleArray, qn as RESPONSE_VARIABLE_NAME_MAX_LENGTH, qt as ConstNumberEditor, r as optionMapping, rn as EqExpressionEditor, rr as SurveyResponse, rt as const_string, s as exportSingleResponseSlot, sn as InRangeExpressionEditor, sr as NumberPrecision, st as ctx_locale, t as formatProjectedValue, tn as CtxPFlagNumEditor, tr as initValueForType, tt as const_number, u as META_COLUMN_ORDER, un as MaxExpressionEditor, ur as isResponseValue, ut as ctx_pflag_num, v as getContentPlainText, vn as analyzeSurveyExpressionReferences, vr as formatCalendarDate, vt as date_gte, w as SurveyTranslations, wn as findIntroducedExpressionReferenceDiagnosticsIncludingAdvisories, wt as gt, x as hasRenderableRichTextContent, xn as collectSurveyExpressionLocations, xt as date_max, y as getPlainTextFromRichTextContent, yn as collectConfigValueExpressions, yr as isCalendarDate, yt as date_lt, z as SurveyItemPrefillTargetType, zn as ExpressionType, zt as str_eq } from "./projection-Bl6qCinx.mjs";
2
2
  import { SURVEY_EDITOR_ITEM_COLORS } from "./editor/colors.mjs";
3
- import { format } from "date-fns";
4
- import { enUS } from "date-fns/locale";
3
+ import { format } from "date-fns/format";
4
+ import { enUS } from "date-fns/locale/en-US";
5
5
  //#region src/to_mirgrate/data_types/legacy-types.ts
6
6
  const isLegacyItemGroupComponent = (item) => {
7
7
  const items = item.items;
@@ -12,530 +12,14 @@ const isLegacySurveyGroupItem = (item) => {
12
12
  return items !== void 0 && items.length > 0;
13
13
  };
14
14
  //#endregion
15
- //#region src/survey/responses/item-response.ts
16
- /**
17
- * SurveyItemResponse to store the response of a survey item.
18
- */
19
- var SurveyItemResponse = class SurveyItemResponse {
20
- itemId;
21
- meta;
22
- response;
23
- itemType;
24
- constructor(itemDef, response) {
25
- this.itemId = itemDef.itemId;
26
- this.response = response;
27
- this.itemType = itemDef.itemType;
28
- }
29
- serialize() {
30
- return {
31
- itemId: this.itemId,
32
- itemType: this.itemType,
33
- meta: this.meta,
34
- response: this.response?.serialize()
35
- };
36
- }
37
- static deserialize(json) {
38
- if (!json || typeof json !== "object" || Array.isArray(json)) throw new Error("Invalid survey item response payload");
39
- if (typeof json.itemId !== "string" || json.itemId.trim().length === 0) throw new Error("Survey item response is missing a valid itemId");
40
- if (typeof json.itemType !== "string" || json.itemType.trim().length === 0) throw new Error("Survey item response is missing a valid itemType");
41
- const newResponse = new SurveyItemResponse({
42
- itemId: json.itemId,
43
- itemType: json.itemType
44
- }, json.response ? ResponseItem.deserialize(json.response) : void 0);
45
- newResponse.meta = json.meta;
46
- return newResponse;
47
- }
48
- };
49
- var ResponseItem = class ResponseItem {
50
- _slots;
51
- constructor(initialValues) {
52
- this._slots = new Map(initialValues ?? []);
53
- }
54
- get(slotId) {
55
- return this._slots.get(slotId);
56
- }
57
- setSlotValue(slotId, slotResponse) {
58
- if (slotResponse) this._slots.set(slotId, slotResponse);
59
- else this._slots.delete(slotId);
60
- }
61
- serialize() {
62
- if (this._slots.size === 0) return;
63
- return { slots: Object.fromEntries(this._slots.entries()) };
64
- }
65
- isEmpty() {
66
- return this._slots.size === 0;
67
- }
68
- clone() {
69
- return new ResponseItem(Array.from(this._slots.entries()));
70
- }
71
- static deserialize(json) {
72
- if (!json || typeof json !== "object" || Array.isArray(json)) throw new Error("Invalid response item payload");
73
- const { slots } = json;
74
- if (!slots || typeof slots !== "object" || Array.isArray(slots)) throw new Error("Invalid response slots payload");
75
- return new ResponseItem(Object.entries(slots).map(([slotId, slotResponse]) => {
76
- if (slotId.trim().length === 0) throw new Error("Response slot id cannot be empty");
77
- assertResponseValue(slotResponse, `response.slots.${slotId}`);
78
- return [slotId, slotResponse];
79
- }));
80
- }
81
- };
82
- //#endregion
83
15
  //#region src/survey/responses/response-meta.ts
84
16
  const SurveyEventTypes = {
85
17
  openSurvey: "openSurvey",
86
18
  pageChanged: "pageChanged",
87
- responeChanged: "responeChanged",
19
+ responseChanged: "responseChanged",
88
20
  languageChanged: "languageChanged"
89
21
  };
90
22
  //#endregion
91
- //#region src/survey/responses/survey-response.ts
92
- const SURVEY_RESPONSE_SCHEMA_VERSION = 1;
93
- /**
94
- * SurveyResponse to store the responses of a survey.
95
- */
96
- var SurveyResponse = class SurveyResponse {
97
- key;
98
- participantId;
99
- submittedAt;
100
- versionId;
101
- responses;
102
- events;
103
- context;
104
- constructor(key, versionId) {
105
- this.key = key;
106
- this.participantId = "";
107
- this.submittedAt = 0;
108
- this.versionId = versionId;
109
- this.responses = /* @__PURE__ */ new Map();
110
- this.events = [];
111
- }
112
- serialize() {
113
- const items = Object.fromEntries(Array.from(this.responses.entries()).flatMap(([itemId, response]) => {
114
- const serialized = response.serialize();
115
- if (!serialized.response && !serialized.meta) return [];
116
- return [[itemId, serialized]];
117
- }));
118
- return {
119
- schemaVersion: 1,
120
- key: this.key,
121
- participantId: this.participantId,
122
- submittedAt: this.submittedAt,
123
- versionId: this.versionId,
124
- items,
125
- events: this.events,
126
- context: this.context
127
- };
128
- }
129
- static deserialize(json) {
130
- if (!json || typeof json !== "object" || Array.isArray(json)) throw new Error("Invalid survey response payload");
131
- if (json.schemaVersion !== 1) throw new Error(`Unsupported survey response schema version: ${String(json.schemaVersion)}`);
132
- if (typeof json.key !== "string" || json.key.trim().length === 0) throw new Error("Survey response is missing a valid key");
133
- if (typeof json.versionId !== "string" || json.versionId.trim().length === 0) throw new Error("Survey response is missing a valid versionId");
134
- if (!json.items || typeof json.items !== "object" || Array.isArray(json.items)) throw new Error("Survey response is missing a valid items object");
135
- const response = new SurveyResponse(json.key, json.versionId);
136
- response.participantId = json.participantId;
137
- response.submittedAt = json.submittedAt;
138
- response.responses = new Map(Object.entries(json.items).map(([itemId, itemResponse]) => {
139
- const deserialized = SurveyItemResponse.deserialize(itemResponse);
140
- if (deserialized.itemId !== itemId) throw new Error(`Survey response item key mismatch for '${itemId}'`);
141
- return [itemId, deserialized];
142
- }));
143
- response.events = json.events ?? [];
144
- response.versionId = json.versionId;
145
- response.key = json.key;
146
- if (json.context) {
147
- if (typeof json.context !== "object" || Array.isArray(json.context)) throw new Error("Survey response context must be an object");
148
- response.context = Object.fromEntries(Object.entries(json.context).map(([contextKey, value]) => {
149
- assertResponseValue(value, `context.${contextKey}`);
150
- return [contextKey, value];
151
- }));
152
- }
153
- return response;
154
- }
155
- };
156
- //#endregion
157
- //#region src/survey/responses/utils.ts
158
- const initValueForType = (returnType) => {
159
- switch (returnType) {
160
- case ValueType.string: return {
161
- type: ValueType.string,
162
- value: ""
163
- };
164
- case ValueType.number: return {
165
- type: ValueType.number,
166
- value: 0
167
- };
168
- case ValueType.boolean: return {
169
- type: ValueType.boolean,
170
- value: false
171
- };
172
- case ValueType.date: return {
173
- type: ValueType.date,
174
- value: Math.floor(Date.now() / 1e3)
175
- };
176
- case ValueType.stringArray: return {
177
- type: ValueType.stringArray,
178
- value: []
179
- };
180
- case ValueType.numberArray: return {
181
- type: ValueType.numberArray,
182
- value: []
183
- };
184
- case ValueType.dateArray: return {
185
- type: ValueType.dateArray,
186
- value: []
187
- };
188
- case ValueType.duration: return {
189
- type: ValueType.duration,
190
- value: 0,
191
- unit: DurationUnits.seconds
192
- };
193
- case ValueType.reference: return {
194
- type: ValueType.reference,
195
- value: ""
196
- };
197
- case ValueType.durationArray: return {
198
- type: ValueType.durationArray,
199
- value: [],
200
- unit: DurationUnits.seconds
201
- };
202
- case ValueType.referenceArray: return {
203
- type: ValueType.referenceArray,
204
- value: []
205
- };
206
- default: throw new Error("Invalid return type: " + returnType);
207
- }
208
- };
209
- //#endregion
210
- //#region src/expressions/expression-evaluator.ts
211
- var ExpressionEvaluator = class {
212
- context;
213
- constructor(context) {
214
- this.context = context;
215
- }
216
- eval(expression) {
217
- if (expression === void 0) return;
218
- switch (expression.type) {
219
- case ExpressionType.Const: return this.evaluateConst(expression);
220
- case ExpressionType.Function: return this.evaluateFunction(expression);
221
- case ExpressionType.ResponseVariable: return this.evaluateResponseVariable(expression);
222
- case ExpressionType.ContextVariable: return this.evaluateContextVariable(expression);
223
- default: throw new Error(`Unsupported expression type: ${expression.type}`);
224
- }
225
- }
226
- setContext(context) {
227
- this.context = context;
228
- }
229
- evaluateConst(expression) {
230
- return expression.value;
231
- }
232
- evaluateResponseVariable(expression) {
233
- const varRef = expression.responseVariableRef;
234
- switch (varRef.name) {
235
- case "get": return this.context?.responses[varRef.itemId]?.response?.get(varRef.slotId);
236
- case "isDefined": return {
237
- type: ValueType.boolean,
238
- value: this.context?.responses[varRef.itemId]?.response?.get(varRef.slotId) !== void 0
239
- };
240
- default: throw new Error(`Unsupported value reference method: ${varRef.name}`);
241
- }
242
- }
243
- evaluateFunction(expression) {
244
- switch (expression.functionName) {
245
- case "and": return this.evaluateAnd(expression);
246
- case "or": return this.evaluateOr(expression);
247
- case "not": return this.evaluateNot(expression);
248
- case "str_eq": return this.evaluateStrEq(expression);
249
- case "eq": return this.evaluateEq(expression);
250
- case "gt": return this.evaluateGt(expression);
251
- case "gte": return this.evaluateGte(expression);
252
- case "lt": return this.evaluateLt(expression);
253
- case "lte": return this.evaluateLte(expression);
254
- case "in_range": return this.evaluateInRange(expression);
255
- case "sum": return this.evaluateSum(expression);
256
- case "min": return this.evaluateMin(expression);
257
- case "max": return this.evaluateMax(expression);
258
- case "list_contains": return this.evaluateListContains(expression);
259
- default: throw new Error(`Unsupported function: ${expression.functionName}`);
260
- }
261
- }
262
- evaluateContextVariable(expression) {
263
- if (!this.context?.surveyContext) return;
264
- const surveyContext = this.context.surveyContext;
265
- switch (expression.contextType) {
266
- case ContextVariableType.Locale: return {
267
- type: ValueType.string,
268
- value: surveyContext.locale
269
- };
270
- case ContextVariableType.ParticipantFlag: {
271
- if (!expression.key) return;
272
- const flagKey = this.eval(expression.key);
273
- if (flagKey?.type !== ValueType.string || flagKey.value === void 0) return;
274
- const flagValue = surveyContext.participantFlags?.[flagKey.value];
275
- switch (expression.asType) {
276
- case ValueType.boolean: return {
277
- type: ValueType.boolean,
278
- value: flagValue !== void 0
279
- };
280
- case ValueType.string:
281
- if (flagValue === void 0) return;
282
- return {
283
- type: ValueType.string,
284
- value: flagValue
285
- };
286
- case ValueType.number: {
287
- if (flagValue === void 0) return;
288
- const numValue = parseFloat(flagValue);
289
- return isNaN(numValue) ? void 0 : {
290
- type: ValueType.number,
291
- value: numValue
292
- };
293
- }
294
- case ValueType.date: {
295
- if (flagValue === void 0) return;
296
- const timestamp = parseFloat(flagValue);
297
- return isNaN(timestamp) ? void 0 : {
298
- type: ValueType.date,
299
- value: timestamp
300
- };
301
- }
302
- default:
303
- if (flagValue === void 0) return;
304
- return {
305
- type: ValueType.string,
306
- value: flagValue
307
- };
308
- }
309
- }
310
- case ContextVariableType.CustomValue: {
311
- if (!expression.key) return;
312
- const customKey = this.eval(expression.key);
313
- if (customKey?.type !== ValueType.string || customKey.value === void 0) return;
314
- const customValue = surveyContext.customValues?.[customKey.value];
315
- if (customValue === void 0) return;
316
- if (expression.asType !== void 0) {
317
- if (customValue?.type !== expression.asType) throw new Error(`Custom value type does not match expected type, got ${customValue?.type} and expected ${expression.asType}`);
318
- }
319
- return customValue;
320
- }
321
- case ContextVariableType.CustomExpression: {
322
- if (!expression.key) return;
323
- const expressionKey = this.eval(expression.key);
324
- if (expressionKey?.type !== ValueType.string || expressionKey.value === void 0) return;
325
- const customExpression = surveyContext.customExpressions?.[expressionKey.value];
326
- if (typeof customExpression !== ExpressionType.Function || customExpression === void 0) return;
327
- try {
328
- const args = expression.arguments;
329
- const result = customExpression(args);
330
- if (expression.asType !== void 0) {
331
- if (result?.type !== expression.asType) throw new Error(`Custom expression result type does not match expected type, got ${result?.type} and expected ${expression.asType}`);
332
- }
333
- return result;
334
- } catch (error) {
335
- throw new Error(`Error evaluating custom expression: ${error}`);
336
- }
337
- }
338
- }
339
- }
340
- evaluateAnd(expression) {
341
- return {
342
- type: ValueType.boolean,
343
- value: expression.arguments.every((arg) => this.eval(arg)?.value === true)
344
- };
345
- }
346
- evaluateOr(expression) {
347
- return {
348
- type: ValueType.boolean,
349
- value: expression.arguments.some((arg) => this.eval(arg)?.value === true)
350
- };
351
- }
352
- evaluateNot(expression) {
353
- if (expression.arguments.length !== 1 || expression.arguments[0] === void 0) throw new Error(`Not function expects 1 argument, got ${expression.arguments.length}`);
354
- const resolvedValue = this.eval(expression.arguments[0]);
355
- if (resolvedValue?.type !== ValueType.boolean) throw new Error(`Not function expects a boolean argument, got ${resolvedValue?.type}`);
356
- if (resolvedValue === void 0) return {
357
- type: ValueType.boolean,
358
- value: false
359
- };
360
- return {
361
- type: ValueType.boolean,
362
- value: !resolvedValue.value
363
- };
364
- }
365
- evaluateListContains(expression) {
366
- if (expression.arguments.length !== 2) throw new Error(`List contains function expects 2 arguments, got ${expression.arguments.length}`);
367
- const resolvedList = this.eval(expression.arguments[0]);
368
- const resolvedItem = this.eval(expression.arguments[1]);
369
- if (resolvedList === void 0 || resolvedItem === void 0) return {
370
- type: ValueType.boolean,
371
- value: false
372
- };
373
- const allowedListTypes = [ValueType.stringArray, ValueType.referenceArray];
374
- const allowedItemTypes = [ValueType.string, ValueType.reference];
375
- const resolvedListType = resolvedList.type;
376
- const resolvedItemType = resolvedItem.type;
377
- if (!allowedListTypes.includes(resolvedListType) || !allowedItemTypes.includes(resolvedItemType)) throw new Error(`List contains function expects a string array list and a string item, got ${resolvedList?.type} and ${resolvedItem?.type}`);
378
- const list = resolvedList.value;
379
- const item = resolvedItem.value;
380
- return {
381
- type: ValueType.boolean,
382
- value: list.includes(item)
383
- };
384
- }
385
- evaluateStrEq(expression) {
386
- if (expression.arguments.length !== 2) throw new Error(`String equals function expects 2 arguments, got ${expression.arguments.length}`);
387
- const resolvedA = this.eval(expression.arguments[0]);
388
- const resolvedB = this.eval(expression.arguments[1]);
389
- if (resolvedA === void 0 || resolvedB === void 0) return {
390
- type: ValueType.boolean,
391
- value: false
392
- };
393
- if (resolvedA?.type !== ValueType.string || resolvedB?.type !== ValueType.string) throw new Error(`String equals function expects strings for all arguments, got ${resolvedA?.type} and ${resolvedB?.type}`);
394
- return {
395
- type: ValueType.boolean,
396
- value: resolvedA.value === resolvedB.value
397
- };
398
- }
399
- evaluateEq(expression) {
400
- if (expression.arguments.length !== 2) throw new Error(`Equals function expects 2 arguments, got ${expression.arguments.length}`);
401
- const resolvedA = this.eval(expression.arguments[0]);
402
- const resolvedB = this.eval(expression.arguments[1]);
403
- if (resolvedA === void 0 || resolvedB === void 0) return {
404
- type: ValueType.boolean,
405
- value: false
406
- };
407
- if (resolvedA?.type !== resolvedB?.type) throw new Error(`Equals function expects the same type for all arguments, got ${resolvedA?.type} and ${resolvedB?.type}`);
408
- return {
409
- type: ValueType.boolean,
410
- value: resolvedA.value === resolvedB.value
411
- };
412
- }
413
- evaluateGt(expression) {
414
- if (expression.arguments.length !== 2) throw new Error(`Greater than function expects 2 arguments, got ${expression.arguments.length}`);
415
- const resolvedA = this.eval(expression.arguments[0]);
416
- const resolvedB = this.eval(expression.arguments[1]);
417
- if (resolvedA === void 0 || resolvedB === void 0) return {
418
- type: ValueType.boolean,
419
- value: false
420
- };
421
- if (resolvedA?.type !== resolvedB?.type) throw new Error(`Greater than function expects the same type for all arguments, got ${resolvedA?.type} and ${resolvedB?.type}`);
422
- return {
423
- type: ValueType.boolean,
424
- value: resolvedA.value > resolvedB.value
425
- };
426
- }
427
- evaluateGte(expression) {
428
- if (expression.arguments.length !== 2) throw new Error(`Greater than or equal to function expects 2 arguments, got ${expression.arguments.length}`);
429
- const resolvedA = this.eval(expression.arguments[0]);
430
- const resolvedB = this.eval(expression.arguments[1]);
431
- if (resolvedA === void 0 || resolvedB === void 0) return {
432
- type: ValueType.boolean,
433
- value: false
434
- };
435
- if (resolvedA?.type !== resolvedB?.type) throw new Error(`Greater than or equal to function expects the same type for all arguments, got ${resolvedA?.type} and ${resolvedB?.type}`);
436
- return {
437
- type: ValueType.boolean,
438
- value: resolvedA.value >= resolvedB.value
439
- };
440
- }
441
- evaluateLt(expression) {
442
- if (expression.arguments.length !== 2) throw new Error(`Less than function expects 2 arguments, got ${expression.arguments.length}`);
443
- const resolvedA = this.eval(expression.arguments[0]);
444
- const resolvedB = this.eval(expression.arguments[1]);
445
- if (resolvedA === void 0 || resolvedB === void 0) return {
446
- type: ValueType.boolean,
447
- value: false
448
- };
449
- if (resolvedA?.type !== resolvedB?.type) throw new Error(`Less than function expects the same type for all arguments, got ${resolvedA?.type} and ${resolvedB?.type}`);
450
- return {
451
- type: ValueType.boolean,
452
- value: resolvedA.value < resolvedB.value
453
- };
454
- }
455
- evaluateLte(expression) {
456
- if (expression.arguments.length !== 2) throw new Error(`Less than or equal to function expects 2 arguments, got ${expression.arguments.length}`);
457
- const resolvedA = this.eval(expression.arguments[0]);
458
- const resolvedB = this.eval(expression.arguments[1]);
459
- if (resolvedA === void 0 || resolvedB === void 0) return {
460
- type: ValueType.boolean,
461
- value: false
462
- };
463
- if (resolvedA?.type !== resolvedB?.type) throw new Error(`Less than or equal to function expects the same type for all arguments, got ${resolvedA?.type} and ${resolvedB?.type}`);
464
- return {
465
- type: ValueType.boolean,
466
- value: resolvedA.value <= resolvedB.value
467
- };
468
- }
469
- evaluateInRange(expression) {
470
- if (expression.arguments.length !== 4) throw new Error(`In range function expects 4 arguments, got ${expression.arguments.length}`);
471
- const resolvedValue = this.eval(expression.arguments[0]);
472
- const resolvedMin = this.eval(expression.arguments[1]);
473
- const resolvedMax = this.eval(expression.arguments[2]);
474
- const resolvedInclusive = this.eval(expression.arguments[3])?.value === true;
475
- if (resolvedValue === void 0 || resolvedMin === void 0 || resolvedMax === void 0) return {
476
- type: ValueType.boolean,
477
- value: false
478
- };
479
- if (resolvedValue?.type !== ValueType.number || resolvedMin?.type !== ValueType.number || resolvedMax?.type !== ValueType.number) throw new Error(`In range function expects numbers for all arguments, got ${resolvedValue?.type} and ${resolvedMin?.type} and ${resolvedMax?.type}`);
480
- if (resolvedInclusive) return {
481
- type: ValueType.boolean,
482
- value: resolvedValue.value >= resolvedMin.value && resolvedValue.value <= resolvedMax.value
483
- };
484
- return {
485
- type: ValueType.boolean,
486
- value: resolvedValue.value > resolvedMin.value && resolvedValue.value < resolvedMax.value
487
- };
488
- }
489
- evaluateSum(expression) {
490
- if (expression.arguments.length < 1) throw new Error(`Sum function expects at least 1 argument, got ${expression.arguments.length}`);
491
- return expression.arguments.reduce((sum, arg) => {
492
- if (arg === void 0) return sum;
493
- const resolvedValue = this.eval(arg);
494
- if (resolvedValue === void 0) return sum;
495
- if (resolvedValue?.type === ValueType.number) return {
496
- type: ValueType.number,
497
- value: sum.value + resolvedValue.value
498
- };
499
- throw new Error(`Sum function expects all arguments to be numbers, got ${resolvedValue?.type}`);
500
- }, {
501
- type: ValueType.number,
502
- value: 0
503
- });
504
- }
505
- evaluateMin(expression) {
506
- if (expression.arguments.length < 1) throw new Error(`Min function expects at least 1 argument, got ${expression.arguments.length}`);
507
- return expression.arguments.reduce((min, arg) => {
508
- if (arg === void 0) return min;
509
- const resolvedValue = this.eval(arg);
510
- if (resolvedValue === void 0) return min;
511
- if (resolvedValue?.type === ValueType.number) return {
512
- type: ValueType.number,
513
- value: Math.min(min.value, resolvedValue.value)
514
- };
515
- throw new Error(`Min function expects all arguments to be numbers, got ${typeof resolvedValue}`);
516
- }, {
517
- type: ValueType.number,
518
- value: Infinity
519
- });
520
- }
521
- evaluateMax(expression) {
522
- if (expression.arguments.length < 1) throw new Error(`Max function expects at least 1 argument, got ${expression.arguments.length}`);
523
- return expression.arguments.reduce((max, arg) => {
524
- if (arg === void 0) return max;
525
- const resolvedValue = this.eval(arg);
526
- if (resolvedValue === void 0) return max;
527
- if (resolvedValue?.type === ValueType.number) return {
528
- type: ValueType.number,
529
- value: Math.max(max.value, resolvedValue.value)
530
- };
531
- throw new Error(`Max function expects all arguments to be numbers, got ${typeof resolvedValue}`);
532
- }, {
533
- type: ValueType.number,
534
- value: -Infinity
535
- });
536
- }
537
- };
538
- //#endregion
539
23
  //#region src/expressions/item-expression-registry.ts
540
24
  function getItemExpressionDefinition(definitions, expressionId) {
541
25
  return definitions?.find((definition) => definition.id === expressionId);
@@ -546,824 +30,144 @@ function buildItemExpression(item, definitions, expressionId, params) {
546
30
  return definition.buildExpression(item, params);
547
31
  }
548
32
  //#endregion
549
- //#region src/expressions/reference-integrity.ts
550
- const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
551
- const isJsonExpression = (value) => isRecord$1(value) && typeof value.type === "string";
552
- const collectItemExpressionLocations = (item) => {
553
- const locations = [];
554
- const add = (expression, location, surface) => {
555
- if (!isJsonExpression(expression)) return;
556
- locations.push({
557
- expression,
558
- location,
559
- ownerItemId: item.id,
560
- ownerItemKey: item.key,
561
- surface
562
- });
563
- };
564
- add(item.displayConditions?.root, "displayConditions.root", "display-condition");
565
- for (const [componentId, expression] of Object.entries(item.displayConditions?.components ?? {})) add(expression, `displayConditions.components.${componentId}`, "display-condition");
566
- for (const [componentId, expression] of Object.entries(item.disabledConditions?.components ?? {})) add(expression, `disabledConditions.components.${componentId}`, "disabled-condition");
567
- for (const [validationKey, expression] of Object.entries(item.validations ?? {})) add(expression, `validations.${validationKey}`, "validation");
568
- for (const [prefillIndex, prefill] of (item.prefills ?? []).entries()) {
569
- const prefillId = prefill.id || String(prefillIndex);
570
- add(prefill.when, `prefills.${prefillId}.when`, "prefill-condition");
571
- if (prefill.source.type === "expression") add(prefill.source.expression, `prefills.${prefillId}.source.expression`, "prefill-source");
572
- }
573
- return locations;
574
- };
575
- const collectSurveyExpressionLocations = (survey) => [...survey.surveyItems.flatMap(collectItemExpressionLocations), ...Object.entries(survey.templateValues ?? {}).flatMap(([templateKey, templateValue]) => templateValue.expression ? [{
576
- expression: templateValue.expression,
577
- location: `templateValues.${templateKey}.expression`,
578
- surface: "template-value"
579
- }] : [])];
580
- const candidateRefs = (unknownRef, availableRefs) => {
581
- let parsed;
582
- try {
583
- parsed = new ValueReference(unknownRef);
584
- } catch {
585
- return availableRefs.slice(0, 5);
586
- }
587
- const sameItemAndMethod = availableRefs.filter((candidate) => {
588
- try {
589
- const candidateRef = new ValueReference(candidate);
590
- return candidateRef.itemId === parsed?.itemId && candidateRef.name === parsed?.name;
591
- } catch {
592
- return false;
33
+ //#region src/engine/active-responses.ts
34
+ const itemNode = (id) => JSON.stringify(["item", id]);
35
+ const componentNode = (id, component) => JSON.stringify([
36
+ "component",
37
+ id,
38
+ component
39
+ ]);
40
+ const slotNode = (id) => JSON.stringify(["answer", id]);
41
+ function assertDeclarativeVisibility(expression) {
42
+ if (!expression) return;
43
+ if (expression.type === ExpressionType.ContextVariable) {
44
+ if (expression.contextType === ContextVariableType.CustomExpression) throw new Error("Visibility conditions cannot use custom callback expressions; provide a serializable custom context value instead.");
45
+ assertDeclarativeVisibility(expression.key);
46
+ }
47
+ if ("arguments" in expression) for (const argument of expression.arguments ?? []) assertDeclarativeVisibility(argument);
48
+ }
49
+ /** Compile the dependencies once; each evaluation reads an immutable logical session snapshot. */
50
+ var ActiveResponsePlan = class {
51
+ survey;
52
+ parents = /* @__PURE__ */ new Map();
53
+ components = /* @__PURE__ */ new Map();
54
+ slots;
55
+ constructor(survey) {
56
+ this.survey = survey;
57
+ this.slots = survey.getResponseSlotRegistry();
58
+ for (const item of survey.surveyItems.values()) {
59
+ if (item.type === "group") for (const child of item.items ?? []) this.parents.set(child, item.id);
60
+ for (const { slotId, componentId } of item.getConditionalResponseSlots()) this.components.set(slotId, [...this.components.get(slotId) ?? [], componentId]);
593
61
  }
594
- });
595
- return (sameItemAndMethod.length > 0 ? sameItemAndMethod : availableRefs).slice(0, 5);
596
- };
597
- const inferExpressionType = (expression, slots) => {
598
- if (!expression) return void 0;
599
- if (expression.type === ExpressionType.Const) return expression.value?.type;
600
- if (expression.type === ExpressionType.ResponseVariable) return slots.get(expression.variableRef)?.valueType;
601
- if (expression.type === ExpressionType.ContextVariable) return expression.contextType === "locale" ? ValueType.string : expression.asType;
602
- switch (expression.functionName) {
603
- case "and":
604
- case "or":
605
- case "not":
606
- case "eq":
607
- case "gt":
608
- case "gte":
609
- case "lt":
610
- case "lte":
611
- case "in_range":
612
- case "list_contains":
613
- case "str_eq":
614
- case "date_eq": return ValueType.boolean;
615
- case "sum":
616
- case "min":
617
- case "max": return ValueType.number;
618
- }
619
- };
620
- const itemTypeForList = (type) => {
621
- switch (type) {
622
- case ValueType.stringArray: return ValueType.string;
623
- case ValueType.referenceArray: return ValueType.reference;
624
- case ValueType.numberArray: return ValueType.number;
625
- case ValueType.dateArray: return ValueType.date;
626
- case ValueType.durationArray: return ValueType.duration;
627
- default: return;
628
- }
629
- };
630
- const inspectFunctionReferenceCompatibility = ({ diagnostics, expression, location, slots }) => {
631
- if (expression.type !== ExpressionType.Function) return;
632
- expression.arguments.forEach((argument) => {
633
- if (argument) inspectFunctionReferenceCompatibility({
634
- diagnostics,
635
- expression: argument,
636
- location,
637
- slots
638
- });
639
- });
640
- const [left, right] = expression.arguments;
641
- const leftType = inferExpressionType(left, slots);
642
- const rightType = inferExpressionType(right, slots);
643
- if (expression.functionName === "list_contains" ? Boolean(leftType && rightType && itemTypeForList(leftType) !== rightType) : expression.functionName === "eq" ? Boolean(leftType && rightType && leftType !== rightType) : false) diagnostics.push({
644
- code: "expression-reference-type-mismatch",
645
- message: `${location.location} compares incompatible values (${leftType} and ${rightType}).`,
646
- location: location.location,
647
- ownerItemId: location.ownerItemId,
648
- ownerItemKey: location.ownerItemKey
649
- });
650
- if (expression.functionName !== "eq" && expression.functionName !== "list_contains") return;
651
- const pairs = [[left, right], [right, left]];
652
- for (const [variable, constant] of pairs) {
653
- if (variable?.type !== ExpressionType.ResponseVariable || constant?.type !== ExpressionType.Const || constant.value?.type !== ValueType.reference) continue;
654
- const slot = slots.get(variable.variableRef);
655
- if (slot?.allowedReferenceValues && !slot.allowedReferenceValues.includes(constant.value.value)) diagnostics.push({
656
- code: "unknown-reference-value",
657
- message: `${location.location} compares "${variable.variableRef}" with missing option "${constant.value.value}".`,
658
- location: location.location,
659
- ownerItemId: location.ownerItemId,
660
- ownerItemKey: location.ownerItemKey,
661
- reference: variable.variableRef,
662
- referenceValue: constant.value.value,
663
- candidates: slot.allowedReferenceValues.slice(0, 5)
664
- });
665
- }
666
- };
667
- const analyzeSurveyExpressionReferences = ({ responseSlots, survey }) => {
668
- const slots = new Map(responseSlots.map((slot) => [slot.ref, slot]));
669
- const availableRefs = responseSlots.map((slot) => slot.ref);
670
- const diagnostics = [];
671
- for (const location of collectSurveyExpressionLocations(survey)) {
672
- let parsed;
673
- try {
674
- parsed = Expression.deserialize(location.expression);
675
- } catch (error) {
676
- diagnostics.push({
677
- code: "invalid-expression",
678
- message: `${location.location} contains invalid expression JSON: ${error instanceof Error ? error.message : String(error)}`,
679
- location: location.location,
680
- ownerItemId: location.ownerItemId,
681
- ownerItemKey: location.ownerItemKey
682
- });
683
- continue;
62
+ const graph = /* @__PURE__ */ new Map();
63
+ const labels = /* @__PURE__ */ new Map();
64
+ const refs = (expression) => expression?.responseVariableRefs.map((ref) => slotNode(ref.slotId)) ?? [];
65
+ for (const item of survey.surveyItems.values()) {
66
+ for (const expression of [item.displayConditions?.root, ...Object.values(item.displayConditions?.components ?? {})]) assertDeclarativeVisibility(expression?.serialize());
67
+ const label = item.metadata?.itemLabel?.trim() || item.id;
68
+ const parent = this.parents.get(item.id);
69
+ graph.set(itemNode(item.id), [...parent ? [itemNode(parent)] : [], ...refs(item.displayConditions?.root)]);
70
+ labels.set(itemNode(item.id), `${label} visibility`);
71
+ for (const [component, expression] of Object.entries(item.displayConditions?.components ?? {})) {
72
+ graph.set(componentNode(item.id, component), [itemNode(item.id), ...refs(expression)]);
73
+ labels.set(componentNode(item.id, component), `${label} (${component}) visibility`);
74
+ }
75
+ for (const slot of item.getResponseSlotDefinitions()) {
76
+ const activation = item.getActiveResponseDependencies(slot.slotId);
77
+ graph.set(slotNode(slot.slotId), [
78
+ itemNode(item.id),
79
+ ...(this.components.get(slot.slotId) ?? []).map((id) => componentNode(item.id, id)),
80
+ ...item.getComputedResponseDependencies(slot.slotId).map(slotNode),
81
+ ...activation.slotIds.map(slotNode),
82
+ ...activation.componentIds.map((id) => componentNode(item.id, id))
83
+ ]);
84
+ labels.set(slotNode(slot.slotId), `${label} (${slot.variableName || slot.slotId})`);
85
+ }
684
86
  }
685
- for (const ref of parsed?.responseVariableRefs ?? []) {
686
- const reference = ref.toString();
687
- if (!slots.has(reference)) diagnostics.push({
688
- code: "unknown-response-ref",
689
- message: `${location.location} references unknown response slot "${reference}".`,
690
- location: location.location,
691
- ownerItemId: location.ownerItemId,
692
- ownerItemKey: location.ownerItemKey,
693
- reference,
694
- candidates: candidateRefs(reference, availableRefs)
87
+ const done = /* @__PURE__ */ new Set(), visiting = /* @__PURE__ */ new Set(), path = [];
88
+ const visit = (node) => {
89
+ if (visiting.has(node)) throw new Error(`Cyclic response/visibility dependency: ${[...path.slice(path.indexOf(node)), node].map((id) => labels.get(id) ?? id).join(" → ")}`);
90
+ if (done.has(node)) return;
91
+ visiting.add(node);
92
+ path.push(node);
93
+ for (const dependency of graph.get(node) ?? []) visit(dependency);
94
+ path.pop();
95
+ visiting.delete(node);
96
+ done.add(node);
97
+ };
98
+ for (const node of graph.keys()) visit(node);
99
+ }
100
+ evaluate(responses, context) {
101
+ const diagnostics = [];
102
+ const cache = /* @__PURE__ */ new Map();
103
+ const visiting = /* @__PURE__ */ new Set();
104
+ const resolve = (key, compute) => {
105
+ if (cache.has(key)) return cache.get(key);
106
+ if (visiting.has(key)) throw new Error(`Cyclic response/visibility dependency: ${key}`);
107
+ visiting.add(key);
108
+ try {
109
+ const value = compute();
110
+ cache.set(key, value);
111
+ return value;
112
+ } finally {
113
+ visiting.delete(key);
114
+ }
115
+ };
116
+ const condition = (itemId, expression, componentId) => {
117
+ if (!expression) return true;
118
+ const value = evaluator.eval(expression);
119
+ if (value !== void 0 && value.type !== ValueType.boolean) diagnostics.push({
120
+ code: "non-boolean-visibility-result",
121
+ itemId,
122
+ ...componentId === void 0 ? {} : { componentId },
123
+ actualType: value.type
695
124
  });
696
- }
697
- inspectFunctionReferenceCompatibility({
698
- diagnostics,
699
- expression: location.expression,
700
- location,
701
- slots
125
+ return value?.type === ValueType.boolean && value.value;
126
+ };
127
+ const isItemVisible = (id) => resolve(itemNode(id), () => {
128
+ const item = this.survey.surveyItems.get(id);
129
+ const parent = this.parents.get(id);
130
+ return !!item && (id === this.survey.rootItem?.id || !!parent && isItemVisible(parent)) && condition(id, item.displayConditions?.root);
702
131
  });
703
- }
704
- return diagnostics;
705
- };
706
- const expressionReferenceDiagnosticKey = (diagnostic) => [
707
- diagnostic.code,
708
- diagnostic.ownerItemId ?? "survey",
709
- diagnostic.location,
710
- diagnostic.reference ?? "",
711
- diagnostic.referenceValue ?? ""
712
- ].join("\0");
713
- const findIntroducedExpressionReferenceDiagnostics = ({ after, before }) => {
714
- const existing = new Set(before.map(expressionReferenceDiagnosticKey));
715
- return after.filter((diagnostic) => !existing.has(expressionReferenceDiagnosticKey(diagnostic)));
716
- };
717
- const replaceResponseVariableReferences = (value, replacements) => {
718
- let changed = false;
719
- const visit = (current) => {
720
- if (Array.isArray(current)) return current.map(visit);
721
- if (!isRecord$1(current)) return current;
722
- const next = Object.fromEntries(Object.entries(current).map(([key, entry]) => [key, visit(entry)]));
723
- if (next.type === ExpressionType.ResponseVariable && typeof next.variableRef === "string" && replacements[next.variableRef]) {
724
- next.variableRef = replacements[next.variableRef];
725
- changed = true;
726
- }
727
- return next;
728
- };
729
- const rewritten = visit(value);
730
- return {
731
- changed,
732
- value: rewritten
733
- };
734
- };
735
- const responseValueReferenceIds = (values) => values?.flatMap((value) => value.type === ValueType.reference ? [value.value] : value.type === ValueType.referenceArray ? value.value : []);
736
- //#endregion
737
- //#region src/expressions/editors/expression-editor.ts
738
- var ExpressionEditor = class {
739
- returnType;
740
- _editorConfig;
741
- get editorConfig() {
742
- return this._editorConfig;
743
- }
744
- withEditorConfig(editorConfig) {
745
- this._editorConfig = editorConfig;
746
- return this;
747
- }
748
- };
749
- var ConstStringArrayEditor = class extends ExpressionEditor {
750
- returnType = ValueType.stringArray;
751
- _values;
752
- constructor(values, editorConfig) {
753
- super();
754
- this._values = values;
755
- this._editorConfig = editorConfig;
756
- }
757
- get values() {
758
- return this._values;
759
- }
760
- set values(values) {
761
- this._values = values;
762
- }
763
- getExpression() {
764
- return new ConstExpression({
765
- type: ValueType.stringArray,
766
- value: this._values
767
- }, this._editorConfig);
768
- }
769
- };
770
- var ConstStringEditor = class extends ExpressionEditor {
771
- returnType = ValueType.string;
772
- _value;
773
- constructor(value, editorConfig) {
774
- super();
775
- this._value = value;
776
- this._editorConfig = editorConfig;
777
- }
778
- get value() {
779
- return this._value;
780
- }
781
- set value(value) {
782
- this._value = value;
783
- }
784
- getExpression() {
785
- return new ConstExpression({
786
- type: ValueType.string,
787
- value: this._value
788
- }, this._editorConfig);
789
- }
790
- };
791
- var ConstNumberEditor = class extends ExpressionEditor {
792
- returnType = ValueType.number;
793
- _value;
794
- constructor(value, editorConfig) {
795
- super();
796
- this._value = value;
797
- this._editorConfig = editorConfig;
798
- }
799
- get value() {
800
- return this._value;
801
- }
802
- set value(value) {
803
- this._value = value;
804
- }
805
- getExpression() {
806
- return new ConstExpression({
807
- type: ValueType.number,
808
- value: this._value
809
- }, this._editorConfig);
810
- }
811
- };
812
- var ConstBooleanEditor = class extends ExpressionEditor {
813
- returnType = ValueType.boolean;
814
- _value;
815
- constructor(value, editorConfig) {
816
- super();
817
- this._value = value;
818
- this._editorConfig = editorConfig;
819
- }
820
- get value() {
821
- return this._value;
822
- }
823
- set value(value) {
824
- this._value = value;
825
- }
826
- getExpression() {
827
- return new ConstExpression({
828
- type: ValueType.boolean,
829
- value: this._value
830
- }, this._editorConfig);
831
- }
832
- };
833
- var ConstDateEditor = class extends ExpressionEditor {
834
- returnType = ValueType.date;
835
- _value;
836
- constructor(value, editorConfig) {
837
- super();
838
- this._value = value;
839
- this._editorConfig = editorConfig;
840
- }
841
- get value() {
842
- return this._value;
843
- }
844
- set value(value) {
845
- this._value = value;
846
- }
847
- getExpression() {
848
- const value = Math.floor(this._value.getTime() / 1e3);
849
- return new ConstExpression({
850
- type: ValueType.date,
851
- value
852
- }, this._editorConfig);
853
- }
854
- };
855
- var ConstNumberArrayEditor = class extends ExpressionEditor {
856
- returnType = ValueType.numberArray;
857
- _values;
858
- constructor(values, editorConfig) {
859
- super();
860
- this._values = values;
861
- this._editorConfig = editorConfig;
862
- }
863
- get values() {
864
- return this._values;
865
- }
866
- set values(values) {
867
- this._values = values;
868
- }
869
- getExpression() {
870
- return new ConstExpression({
871
- type: ValueType.numberArray,
872
- value: this._values
873
- }, this._editorConfig);
874
- }
875
- };
876
- var ConstDateArrayEditor = class extends ExpressionEditor {
877
- returnType = ValueType.dateArray;
878
- _values;
879
- constructor(values, editorConfig) {
880
- super();
881
- this._values = values;
882
- this._editorConfig = editorConfig;
883
- }
884
- get values() {
885
- return this._values;
886
- }
887
- set values(values) {
888
- this._values = values;
889
- }
890
- getExpression() {
891
- const value = this._values.map((date) => Math.floor(date.getTime() / 1e3));
892
- return new ConstExpression({
893
- type: ValueType.dateArray,
894
- value
895
- }, this._editorConfig);
896
- }
897
- };
898
- var ResponseVariableEditor = class extends ExpressionEditor {
899
- _variableName;
900
- _variableRef;
901
- constructor(variableName, variableType, editorConfig) {
902
- super();
903
- this._variableName = variableName;
904
- this._variableRef = new ValueReference(variableName);
905
- this._editorConfig = editorConfig;
906
- this.returnType = variableType;
907
- }
908
- get variableName() {
909
- return this._variableName;
910
- }
911
- get variableRef() {
912
- return this._variableRef;
913
- }
914
- getExpression() {
915
- return new ResponseVariableExpression(this._variableName, this._editorConfig);
916
- }
917
- };
918
- var ContextVariableEditor = class extends ExpressionEditor {
919
- _contextType;
920
- _key;
921
- _args;
922
- _asType;
923
- constructor(contextType, key, args, asType, editorConfig) {
924
- super();
925
- this._contextType = contextType;
926
- this._key = key;
927
- this._args = args;
928
- this._asType = asType;
929
- this._editorConfig = editorConfig;
930
- }
931
- getExpression() {
932
- return new ContextVariableExpression(this._contextType, this._key?.getExpression(), this._args?.map((arg) => arg.getExpression()), this._asType, this._editorConfig);
933
- }
934
- };
935
- var CtxLocaleEditor = class extends ContextVariableEditor {
936
- returnType = ValueType.string;
937
- constructor(editorConfig) {
938
- super(ContextVariableType.Locale, void 0, void 0, ValueType.string, editorConfig);
939
- }
940
- };
941
- var CtxPFlagIsDefinedEditor = class extends ContextVariableEditor {
942
- returnType = ValueType.boolean;
943
- constructor(key, editorConfig) {
944
- super(ContextVariableType.ParticipantFlag, key, void 0, ValueType.boolean, editorConfig);
945
- }
946
- };
947
- var CtxPFlagStringEditor = class extends ContextVariableEditor {
948
- returnType = ValueType.string;
949
- constructor(key, editorConfig) {
950
- super(ContextVariableType.ParticipantFlag, key, void 0, ValueType.string, editorConfig);
951
- }
952
- };
953
- var CtxPFlagNumEditor = class extends ContextVariableEditor {
954
- returnType = ValueType.number;
955
- constructor(key, editorConfig) {
956
- super(ContextVariableType.ParticipantFlag, key, void 0, ValueType.number, editorConfig);
957
- }
958
- };
959
- var CtxPFlagDateEditor = class extends ContextVariableEditor {
960
- returnType = ValueType.date;
961
- constructor(key, editorConfig) {
962
- super(ContextVariableType.ParticipantFlag, key, void 0, ValueType.date, editorConfig);
963
- }
964
- };
965
- var CtxCustomValueEditor = class extends ContextVariableEditor {
966
- constructor(key, asType, editorConfig) {
967
- super(ContextVariableType.CustomValue, key, void 0, asType, editorConfig);
968
- this.returnType = asType;
969
- }
970
- };
971
- var CtxCustomExpressionEditor = class extends ContextVariableEditor {
972
- constructor(key, args, asType, editorConfig) {
973
- super(ContextVariableType.CustomExpression, key, args, asType, editorConfig);
974
- this.returnType = asType;
975
- }
976
- };
977
- var GroupExpressionEditor = class extends ExpressionEditor {
978
- _args;
979
- constructor(args, editorConfig) {
980
- super();
981
- this._args = args;
982
- this._editorConfig = editorConfig;
983
- }
984
- get args() {
985
- return this._args;
986
- }
987
- addArg(arg, position) {
988
- if (position === void 0) this._args.push(arg);
989
- else this._args.splice(position, 0, arg);
990
- }
991
- removeArg(position) {
992
- this._args.splice(position, 1);
993
- }
994
- replaceArg(position, arg) {
995
- this._args[position] = arg;
996
- }
997
- swapArgs(activeIndex, overIndex) {
998
- const newOrder = [...this._args];
999
- newOrder.splice(activeIndex, 1);
1000
- newOrder.splice(overIndex, 0, this._args[activeIndex]);
1001
- this._args = newOrder;
1002
- }
1003
- };
1004
- var AndExpressionEditor = class extends GroupExpressionEditor {
1005
- returnType = ValueType.boolean;
1006
- constructor(args, editorConfig) {
1007
- if (args.some((arg) => arg.returnType !== ValueType.boolean)) throw new Error("And expression editor must have boolean arguments");
1008
- super(args, editorConfig);
1009
- }
1010
- getExpression() {
1011
- return new FunctionExpression("and", this.args.map((arg) => arg.getExpression()), this._editorConfig);
1012
- }
1013
- };
1014
- var OrExpressionEditor = class extends GroupExpressionEditor {
1015
- returnType = ValueType.boolean;
1016
- constructor(args, editorConfig) {
1017
- if (args.some((arg) => arg.returnType !== ValueType.boolean)) throw new Error("Or expression editor must have boolean arguments");
1018
- super(args, editorConfig);
1019
- }
1020
- getExpression() {
1021
- return new FunctionExpression("or", this.args.map((arg) => arg.getExpression()), this._editorConfig);
1022
- }
1023
- };
1024
- var StrListContainsExpressionEditor = class extends ExpressionEditor {
1025
- returnType = ValueType.boolean;
1026
- _list;
1027
- _item;
1028
- constructor(list, item, editorConfig) {
1029
- super();
1030
- if (list.returnType !== ValueType.stringArray) throw new Error("List contains expression editor must have a string array list");
1031
- if (item.returnType !== ValueType.string) throw new Error("List contains expression editor must have a string item");
1032
- this._list = list;
1033
- this._item = item;
1034
- this._editorConfig = editorConfig;
1035
- }
1036
- get list() {
1037
- return this._list;
1038
- }
1039
- get item() {
1040
- return this._item;
1041
- }
1042
- set list(list) {
1043
- this._list = list;
1044
- }
1045
- set item(item) {
1046
- this._item = item;
1047
- }
1048
- getExpression() {
1049
- return new FunctionExpression("list_contains", [this._list?.getExpression(), this._item?.getExpression()], this._editorConfig);
1050
- }
1051
- };
1052
- var StrEqExpressionEditor = class extends ExpressionEditor {
1053
- returnType = ValueType.boolean;
1054
- _a;
1055
- _b;
1056
- constructor(a, b, editorConfig) {
1057
- super();
1058
- this._a = a;
1059
- this._b = b;
1060
- this._editorConfig = editorConfig;
1061
- }
1062
- get a() {
1063
- return this._a;
1064
- }
1065
- get b() {
1066
- return this._b;
1067
- }
1068
- set a(a) {
1069
- this._a = a;
1070
- }
1071
- set b(b) {
1072
- this._b = b;
1073
- }
1074
- getExpression() {
1075
- return new FunctionExpression("str_eq", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
1076
- }
1077
- };
1078
- var EqExpressionEditor = class extends ExpressionEditor {
1079
- returnType = ValueType.boolean;
1080
- _a;
1081
- _b;
1082
- constructor(a, b, editorConfig) {
1083
- super();
1084
- this._a = a;
1085
- this._b = b;
1086
- this._editorConfig = editorConfig;
1087
- }
1088
- get a() {
1089
- return this._a;
1090
- }
1091
- get b() {
1092
- return this._b;
1093
- }
1094
- set a(a) {
1095
- this._a = a;
1096
- }
1097
- set b(b) {
1098
- this._b = b;
1099
- }
1100
- getExpression() {
1101
- return new FunctionExpression("eq", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
1102
- }
1103
- };
1104
- var GtExpressionEditor = class extends ExpressionEditor {
1105
- returnType = ValueType.boolean;
1106
- _a;
1107
- _b;
1108
- constructor(a, b, editorConfig) {
1109
- super();
1110
- this._a = a;
1111
- this._b = b;
1112
- this._editorConfig = editorConfig;
1113
- }
1114
- get a() {
1115
- return this._a;
1116
- }
1117
- get b() {
1118
- return this._b;
1119
- }
1120
- set a(a) {
1121
- this._a = a;
1122
- }
1123
- set b(b) {
1124
- this._b = b;
1125
- }
1126
- getExpression() {
1127
- return new FunctionExpression("gt", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
1128
- }
1129
- };
1130
- var GteExpressionEditor = class extends ExpressionEditor {
1131
- returnType = ValueType.boolean;
1132
- _a;
1133
- _b;
1134
- constructor(a, b, editorConfig) {
1135
- super();
1136
- this._a = a;
1137
- this._b = b;
1138
- this._editorConfig = editorConfig;
1139
- }
1140
- get a() {
1141
- return this._a;
1142
- }
1143
- get b() {
1144
- return this._b;
1145
- }
1146
- set a(a) {
1147
- this._a = a;
1148
- }
1149
- set b(b) {
1150
- this._b = b;
1151
- }
1152
- getExpression() {
1153
- return new FunctionExpression("gte", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
1154
- }
1155
- };
1156
- var LtExpressionEditor = class extends ExpressionEditor {
1157
- returnType = ValueType.boolean;
1158
- _a;
1159
- _b;
1160
- constructor(a, b, editorConfig) {
1161
- super();
1162
- this._a = a;
1163
- this._b = b;
1164
- this._editorConfig = editorConfig;
1165
- }
1166
- get a() {
1167
- return this._a;
1168
- }
1169
- get b() {
1170
- return this._b;
1171
- }
1172
- set a(a) {
1173
- this._a = a;
1174
- }
1175
- set b(b) {
1176
- this._b = b;
1177
- }
1178
- getExpression() {
1179
- return new FunctionExpression("lt", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
1180
- }
1181
- };
1182
- var LteExpressionEditor = class extends ExpressionEditor {
1183
- returnType = ValueType.boolean;
1184
- _a;
1185
- _b;
1186
- constructor(a, b, editorConfig) {
1187
- super();
1188
- this._a = a;
1189
- this._b = b;
1190
- this._editorConfig = editorConfig;
1191
- }
1192
- get a() {
1193
- return this._a;
1194
- }
1195
- get b() {
1196
- return this._b;
1197
- }
1198
- set b(b) {
1199
- this._b = b;
1200
- }
1201
- set a(a) {
1202
- this._a = a;
1203
- }
1204
- getExpression() {
1205
- return new FunctionExpression("lte", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
1206
- }
1207
- };
1208
- var InRangeExpressionEditor = class extends ExpressionEditor {
1209
- returnType = ValueType.boolean;
1210
- value;
1211
- min;
1212
- max;
1213
- inclusive;
1214
- constructor(value, min, max, inclusive, editorConfig) {
1215
- super();
1216
- this.value = value;
1217
- this.min = min;
1218
- this.max = max;
1219
- this.inclusive = inclusive;
1220
- this._editorConfig = editorConfig;
1221
- }
1222
- getExpression() {
1223
- return new FunctionExpression("in_range", [
1224
- this.value?.getExpression(),
1225
- this.min?.getExpression(),
1226
- this.max?.getExpression(),
1227
- this.inclusive?.getExpression()
1228
- ], this._editorConfig);
1229
- }
1230
- };
1231
- var SumExpressionEditor = class extends GroupExpressionEditor {
1232
- returnType = ValueType.number;
1233
- constructor(args, editorConfig) {
1234
- super(args, editorConfig);
1235
- }
1236
- getExpression() {
1237
- return new FunctionExpression("sum", this.args.map((arg) => arg.getExpression()), this._editorConfig);
1238
- }
1239
- };
1240
- var MinExpressionEditor = class extends GroupExpressionEditor {
1241
- returnType = ValueType.number;
1242
- constructor(args, editorConfig) {
1243
- super(args, editorConfig);
1244
- }
1245
- getExpression() {
1246
- return new FunctionExpression("min", this.args.map((arg) => arg.getExpression()), this._editorConfig);
1247
- }
1248
- };
1249
- var MaxExpressionEditor = class extends GroupExpressionEditor {
1250
- returnType = ValueType.number;
1251
- constructor(args, editorConfig) {
1252
- super(args, editorConfig);
1253
- }
1254
- getExpression() {
1255
- return new FunctionExpression("max", this.args.map((arg) => arg.getExpression()), this._editorConfig);
132
+ const isComponentVisible = (id, component) => resolve(componentNode(id, component), () => isItemVisible(id) && condition(id, this.survey.surveyItems.get(id)?.displayConditions?.components?.[component], component));
133
+ const getAnswer = (slotId) => resolve(slotNode(slotId), () => {
134
+ const owner = this.slots.get(slotId);
135
+ if (!owner || !isItemVisible(owner.itemId) || !(this.components.get(slotId) ?? []).every((id) => isComponentVisible(owner.itemId, id))) return void 0;
136
+ const item = this.survey.surveyItems.get(owner.itemId);
137
+ if (!owner.computed) return item.getActiveResponseValue(slotId, responses[owner.itemId]?.response?.get(slotId), (id) => isComponentVisible(owner.itemId, id), getAnswer);
138
+ const sources = new ResponseItem();
139
+ for (const id of item.getComputedResponseDependencies(slotId)) sources.setSlotValue(id, getAnswer(id));
140
+ return item.getComputedResponseValue(slotId, sources, (id) => isComponentVisible(owner.itemId, id));
141
+ });
142
+ const evaluator = new ExpressionEvaluator({
143
+ responses,
144
+ surveyContext: context,
145
+ resolveResponseValue: getAnswer
146
+ });
147
+ const getItemResponse = (id) => {
148
+ const response = new ResponseItem();
149
+ for (const slot of this.survey.surveyItems.get(id)?.getResponseSlotDefinitions() ?? []) if (!slot.computed) response.setSlotValue(slot.slotId, getAnswer(slot.slotId));
150
+ return response.isEmpty() ? void 0 : response;
151
+ };
152
+ return {
153
+ evaluator,
154
+ getAnswer,
155
+ getItemResponse,
156
+ isItemVisible,
157
+ isComponentVisible,
158
+ getDiagnostics: () => diagnostics.map((diagnostic) => ({ ...diagnostic }))
159
+ };
1256
160
  }
1257
161
  };
1258
162
  //#endregion
1259
- //#region src/expressions/editors/expression-editor-generators.ts
1260
- const const_string_array = (...values) => {
1261
- return new ConstStringArrayEditor(values);
1262
- };
1263
- const const_string = (value) => {
1264
- return new ConstStringEditor(value);
1265
- };
1266
- const const_number_array = (...values) => {
1267
- return new ConstNumberArrayEditor(values);
1268
- };
1269
- const const_number = (value) => {
1270
- return new ConstNumberEditor(value);
1271
- };
1272
- const const_boolean = (value) => {
1273
- return new ConstBooleanEditor(value);
1274
- };
1275
- const const_date = (value) => {
1276
- return new ConstDateEditor(value);
1277
- };
1278
- const const_date_array = (...values) => {
1279
- return new ConstDateArrayEditor(values);
1280
- };
1281
- const response_string = (valueRef) => {
1282
- return new ResponseVariableEditor(valueRef, ValueType.string);
1283
- };
1284
- const response_string_array = (valueRef) => {
1285
- return new ResponseVariableEditor(valueRef, ValueType.stringArray);
1286
- };
1287
- const response_number = (valueRef) => {
1288
- return new ResponseVariableEditor(valueRef, ValueType.number);
1289
- };
1290
- const response_boolean = (valueRef) => {
1291
- return new ResponseVariableEditor(valueRef, ValueType.boolean);
1292
- };
1293
- const response_date = (valueRef) => {
1294
- return new ResponseVariableEditor(valueRef, ValueType.date);
1295
- };
1296
- const response_number_array = (valueRef) => {
1297
- return new ResponseVariableEditor(valueRef, ValueType.numberArray);
1298
- };
1299
- const response_date_array = (valueRef) => {
1300
- return new ResponseVariableEditor(valueRef, ValueType.dateArray);
1301
- };
1302
- const ctx_locale = () => {
1303
- return new CtxLocaleEditor();
1304
- };
1305
- const ctx_pflag_is_defined = (key) => {
1306
- return new CtxPFlagIsDefinedEditor(key);
1307
- };
1308
- const ctx_pflag_string = (key) => {
1309
- return new CtxPFlagStringEditor(key);
1310
- };
1311
- const ctx_pflag_num = (key) => {
1312
- return new CtxPFlagNumEditor(key);
1313
- };
1314
- const ctx_pflag_date = (key) => {
1315
- return new CtxPFlagDateEditor(key);
1316
- };
1317
- const ctx_custom_value = (key, dType) => {
1318
- return new CtxCustomValueEditor(key, dType);
1319
- };
1320
- const ctx_custom_expression = (key, args, returnType) => {
1321
- return new CtxCustomExpressionEditor(key, args, returnType);
1322
- };
1323
- const and = (...args) => {
1324
- return new AndExpressionEditor(args);
1325
- };
1326
- const or = (...args) => {
1327
- return new OrExpressionEditor(args);
1328
- };
1329
- const str_list_contains = (list, item) => {
1330
- return new StrListContainsExpressionEditor(list, item);
1331
- };
1332
- const str_eq = (a, b) => {
1333
- return new StrEqExpressionEditor(a, b);
1334
- };
1335
- const eq = (a, b) => {
1336
- return new EqExpressionEditor(a, b);
1337
- };
1338
- const gt = (a, b) => {
1339
- return new GtExpressionEditor(a, b);
1340
- };
1341
- const gte = (a, b) => {
1342
- return new GteExpressionEditor(a, b);
1343
- };
1344
- const lt = (a, b) => {
1345
- return new LtExpressionEditor(a, b);
1346
- };
1347
- const lte = (a, b) => {
1348
- return new LteExpressionEditor(a, b);
1349
- };
1350
- const in_range = (value, min, max, inclusive) => {
1351
- return new InRangeExpressionEditor(value, min, max, inclusive);
1352
- };
1353
- const sum = (...args) => {
1354
- return new SumExpressionEditor(args);
1355
- };
1356
- const min = (...args) => {
1357
- return new MinExpressionEditor(args);
1358
- };
1359
- const max = (...args) => {
1360
- return new MaxExpressionEditor(args);
1361
- };
1362
- //#endregion
1363
163
  //#region src/engine/engine.ts
1364
- var SurveyEngineCore = class {
164
+ var SurveyEngineCore = class SurveyEngineCore {
165
+ activeResponsePlan;
166
+ activeState;
167
+ responseDiagnostics = /* @__PURE__ */ new Map();
1365
168
  surveyDef;
1366
169
  renderedSurveyTree;
170
+ renderedItemsCache;
1367
171
  context;
1368
172
  locale;
1369
173
  sessionSeedValue;
@@ -1378,12 +182,24 @@ var SurveyEngineCore = class {
1378
182
  this._openedAt = this._getTimestamp();
1379
183
  this.sessionSeedValue = generateId();
1380
184
  this.surveyDef = survey;
185
+ const { issues: configurationIssues, plan } = SurveyEngineCore.inspectConfiguration(survey);
186
+ if (configurationIssues.length) throw new Error(configurationIssues.join("\n"));
187
+ this.activeResponsePlan = plan;
1381
188
  this.context = context ? context : { locale: "en" };
1382
189
  this.locale = this.context.locale;
1383
- this.prefills = prefills ? prefills.reduce((acc, p) => {
1384
- acc[p.itemId] = SurveyItemResponse.deserialize(p);
1385
- return acc;
1386
- }, {}) : void 0;
190
+ if (prefills) {
191
+ const registry = survey.getResponseSlotRegistry();
192
+ this.prefills = Object.create(null);
193
+ for (const [slotId, value] of Object.entries(prefills)) {
194
+ const owner = registry.get(slotId);
195
+ if (!owner || owner.computed) throw new Error(`Cannot restore unknown or computed response slot "${slotId}"`);
196
+ const item = survey.surveyItems.get(owner.itemId);
197
+ (this.prefills[owner.itemId] ??= new SurveyItemResponse({
198
+ itemId: item.id,
199
+ itemType: item.type
200
+ }, new ResponseItem())).response.setSlotValue(slotId, value);
201
+ }
202
+ }
1387
203
  this.prefillEnvironment = options?.prefillEnvironment;
1388
204
  this.dateLocales = dateLocales || [{
1389
205
  code: "en",
@@ -1391,7 +207,7 @@ var SurveyEngineCore = class {
1391
207
  }];
1392
208
  this.responses = this.initResponseObject(this.surveyDef.surveyItems);
1393
209
  this.initCache();
1394
- this.applyConfiguredPrefills();
210
+ if (options?.applyConfiguredPrefills !== false) this.applyConfiguredPrefills();
1395
211
  this.evalExpressions();
1396
212
  if (!survey.rootItem) throw new Error("constructor: root item not found");
1397
213
  this.renderedSurveyTree = this.renderGroup(survey.rootItem);
@@ -1402,7 +218,7 @@ var SurveyEngineCore = class {
1402
218
  }];
1403
219
  }
1404
220
  setContext(context) {
1405
- this.context = context;
221
+ this.updateContext(context);
1406
222
  }
1407
223
  getDateLocales() {
1408
224
  return this.dateLocales.slice();
@@ -1417,9 +233,10 @@ var SurveyEngineCore = class {
1417
233
  return found?.locale;
1418
234
  }
1419
235
  updateContext(context) {
236
+ const localeChanged = this.locale !== context.locale;
1420
237
  this.context = context;
1421
238
  this.locale = context.locale;
1422
- this._registerSurveyEvent({
239
+ if (localeChanged) this._registerSurveyEvent({
1423
240
  type: SurveyEventTypes.languageChanged,
1424
241
  timestamp: this._getTimestamp(),
1425
242
  value: context.locale
@@ -1427,21 +244,49 @@ var SurveyEngineCore = class {
1427
244
  this.evalExpressions();
1428
245
  this.reRenderSurveyTree();
1429
246
  }
247
+ /** Change the presentation language without losing participant/custom context. */
248
+ updateLocale(locale) {
249
+ if (this.context.locale === locale) return false;
250
+ this.updateContext({
251
+ ...this.context,
252
+ locale
253
+ });
254
+ return true;
255
+ }
1430
256
  setResponse(targetKey, response) {
1431
257
  const target = this.getResponseItem(targetKey);
1432
258
  if (!target) throw new Error("setResponse: target not found for key: " + targetKey);
1433
- target.response = response;
1434
- this._registerSurveyEvent({
1435
- type: SurveyEventTypes.responeChanged,
1436
- timestamp: this._getTimestamp(),
1437
- itemId: targetKey
1438
- });
259
+ const item = this.surveyDef.surveyItems.get(targetKey);
260
+ const previousSlots = structuredClone(target.response?.serialize()?.slots ?? {});
261
+ const normalized = item.normalizeResponse(response);
262
+ target.response = normalized.response;
263
+ this.responseDiagnostics.set(targetKey, normalized.diagnostics ?? []);
264
+ const nextSlots = target.response?.serialize()?.slots ?? {};
265
+ const timestamp = this._getTimestamp();
266
+ const comparable = (value) => value === void 0 ? void 0 : JSON.stringify(value, Object.keys(value).sort());
267
+ for (const slotId of /* @__PURE__ */ new Set([...Object.keys(previousSlots), ...Object.keys(nextSlots)])) {
268
+ if (comparable(previousSlots[slotId]) === comparable(nextSlots[slotId])) continue;
269
+ this._registerSurveyEvent({
270
+ type: SurveyEventTypes.responseChanged,
271
+ timestamp,
272
+ slotId,
273
+ operation: nextSlots[slotId] === void 0 ? "clear" : "set"
274
+ });
275
+ }
1439
276
  this.evalExpressions();
1440
277
  this.reRenderSurveyTree();
1441
278
  }
1442
279
  get openedAt() {
1443
280
  return this._openedAt;
1444
281
  }
282
+ /** Invalid visibility results in the current evaluation; unanswered conditions are omitted. */
283
+ getEvaluationDiagnostics() {
284
+ return this.activeEvaluation().getDiagnostics();
285
+ }
286
+ /** Diagnostics from the latest initialization or response update for an item. */
287
+ getResponseDiagnostics(itemId) {
288
+ return (this.responseDiagnostics.get(itemId) ?? []).map((diagnostic) => ({ ...diagnostic }));
289
+ }
1445
290
  get sessionSeed() {
1446
291
  return this.sessionSeedValue;
1447
292
  }
@@ -1468,9 +313,7 @@ var SurveyEngineCore = class {
1468
313
  case "large":
1469
314
  max = this.surveyDef.maxItemsPerPage.large;
1470
315
  break;
1471
- case "small":
1472
- max = this.surveyDef.maxItemsPerPage.small;
1473
- break;
316
+ case "small": max = this.surveyDef.maxItemsPerPage.small;
1474
317
  }
1475
318
  if (currentPage.length >= max) {
1476
319
  pages.push([...currentPage]);
@@ -1480,8 +323,17 @@ var SurveyEngineCore = class {
1480
323
  if (currentPage.length > 0) pages.push([...currentPage]);
1481
324
  return pages;
1482
325
  }
326
+ /** Read-only projection, with stable identity until the engine renders a new tree. */
327
+ getRenderedSurveyItems() {
328
+ if (this.renderedItemsCache?.tree !== this.renderedSurveyTree) this.renderedItemsCache = {
329
+ tree: this.renderedSurveyTree,
330
+ items: Object.freeze(flattenTree(this.renderedSurveyTree).map((item) => Object.freeze(item)))
331
+ };
332
+ return this.renderedItemsCache.items;
333
+ }
1483
334
  getRenderedSurveyItem(itemId) {
1484
- return flattenTree(this.renderedSurveyTree).find((item) => item.id === itemId);
335
+ const item = this.getRenderedSurveyItems().find((item) => item.id === itemId);
336
+ return item ? { ...item } : void 0;
1485
337
  }
1486
338
  onPageChanged(pageIndex) {
1487
339
  this._registerSurveyEvent({
@@ -1493,11 +345,44 @@ var SurveyEngineCore = class {
1493
345
  getEvents() {
1494
346
  return this._events.slice();
1495
347
  }
348
+ /** Current session answers, including retained answers to hidden items. */
1496
349
  getResponses() {
350
+ return this.collectResponses(true);
351
+ }
352
+ /** Answers to currently visible items and response slots on every page; does not erase hidden session answers. */
353
+ getSubmissionResponses() {
354
+ return this.collectResponses(false);
355
+ }
356
+ /** Flat submission with events and item metadata kept separate from answer identity. */
357
+ getSurveyResponse(key, includeHidden = false) {
358
+ const response = new SurveyResponse(key);
359
+ for (const item of this.collectResponses(includeHidden)) response.addItemResponse(item);
360
+ response.evaluationContext = structuredClone({
361
+ locale: this.context.locale,
362
+ participantFlags: this.context.participantFlags
363
+ });
364
+ response.events = structuredClone(this._events);
365
+ response.context = this.context.customValues && structuredClone(this.context.customValues);
366
+ return response;
367
+ }
368
+ getAnswer(slotId) {
369
+ const owner = this.surveyDef.getResponseSlotRegistry().get(slotId);
370
+ if (!owner) throw new Error(`Unknown response slot "${slotId}"`);
371
+ return owner.computed ? this.getComputedResponseValue(slotId) : this.responses[owner.itemId]?.response?.get(slotId);
372
+ }
373
+ setAnswer(slotId, value) {
374
+ const owner = this.surveyDef.getResponseSlotRegistry().get(slotId);
375
+ if (!owner || owner.computed) throw new Error(`Cannot write unknown or computed response slot "${slotId}"`);
376
+ const response = this.responses[owner.itemId]?.response?.clone() ?? new ResponseItem();
377
+ response.setSlotValue(slotId, value);
378
+ this.setResponse(owner.itemId, response);
379
+ }
380
+ collectResponses(includeHidden) {
1497
381
  const renderedPositions = /* @__PURE__ */ new Map();
1498
382
  flattenTree(this.renderedSurveyTree).filter((item) => item.type !== "page-break").forEach((item, index) => {
1499
383
  if (this.responses[item.id]) renderedPositions.set(item.id, index);
1500
384
  });
385
+ const active = includeHidden ? void 0 : this.activeEvaluation();
1501
386
  const responses = [];
1502
387
  this.surveyDef.surveyItems.forEach((item, itemId) => {
1503
388
  if (!item.isInteractive()) return;
@@ -1505,10 +390,31 @@ var SurveyEngineCore = class {
1505
390
  if (!response) return;
1506
391
  const position = renderedPositions.get(itemId);
1507
392
  response.meta = position === void 0 ? void 0 : { position };
1508
- responses.push(response);
393
+ if (!includeHidden && position === void 0) return;
394
+ if (includeHidden) {
395
+ responses.push(response);
396
+ return;
397
+ }
398
+ const submission = new SurveyItemResponse(response, active.getItemResponse(itemId));
399
+ submission.meta = response.meta && { ...response.meta };
400
+ responses.push(submission);
1509
401
  });
1510
402
  return responses;
1511
403
  }
404
+ /** Author and intrinsic validation failures, restricted to currently rendered items. */
405
+ getInvalidResponseItems(itemIds) {
406
+ const rendered = new Set(this.getRenderedSurveyItems().map((item) => item.id));
407
+ return [...new Set(itemIds ?? rendered)].filter((id) => rendered.has(id) && Object.values(this.getValidationValues(id) ?? {}).some((valid) => !valid));
408
+ }
409
+ getIntrinsicValidationValues(itemKey) {
410
+ const active = this.activeEvaluation();
411
+ if (!active.isItemVisible(itemKey)) return {};
412
+ return { ...this.surveyDef.surveyItems.get(itemKey)?.getResponseValidationValues(active.getItemResponse(itemKey), (id) => active.isComponentVisible(itemKey, id), (expression) => active.evaluator.eval(Expression.deserialize(expression))) };
413
+ }
414
+ getAuthorValidationValues(itemKey) {
415
+ if (!this.activeEvaluation().isItemVisible(itemKey)) return {};
416
+ return Object.fromEntries(Object.entries(this.cache.validations[itemKey] ?? {}).map(([key, validation]) => [key, validation.result]));
417
+ }
1512
418
  getDisplayConditionValue(itemKey, componentKey) {
1513
419
  if (componentKey) return this.cache.displayConditions[itemKey]?.components?.[componentKey]?.result;
1514
420
  return this.cache.displayConditions[itemKey]?.root?.result;
@@ -1520,12 +426,9 @@ var SurveyEngineCore = class {
1520
426
  return this.cache.templateValues[templateValueKey]?.value;
1521
427
  }
1522
428
  getValidationValues(itemKey) {
1523
- const validations = this.cache.validations[itemKey];
1524
- if (!validations) return;
1525
- return Object.keys(validations).reduce((acc, validationKey) => {
1526
- acc[validationKey] = validations[validationKey].result;
1527
- return acc;
1528
- }, {});
429
+ const result = this.getIntrinsicValidationValues(itemKey);
430
+ for (const [key, valid] of Object.entries(this.getAuthorValidationValues(itemKey))) result[key] = valid && (result[key] ?? true);
431
+ return Object.keys(result).length ? result : void 0;
1529
432
  }
1530
433
  initCache() {
1531
434
  this.cache = {
@@ -1595,40 +498,39 @@ var SurveyEngineCore = class {
1595
498
  initResponseObject(items) {
1596
499
  const respGroup = {};
1597
500
  items.forEach((item, itemId) => {
1598
- if (!item.isInteractive) return;
501
+ if (!item.isInteractive()) return;
1599
502
  else {
1600
503
  const prefill = this.prefills?.[itemId];
1601
504
  const applyPrefill = prefill && prefill.itemType === item.type;
505
+ const normalized = item.normalizeResponse(applyPrefill ? prefill.response : void 0);
506
+ this.responseDiagnostics.set(itemId, normalized.diagnostics ?? []);
1602
507
  respGroup[itemId] = new SurveyItemResponse({
1603
508
  itemId,
1604
509
  itemType: item.type
1605
- }, applyPrefill ? prefill.response : void 0);
510
+ }, normalized.response);
1606
511
  }
1607
512
  });
1608
513
  return respGroup;
1609
514
  }
1610
515
  applyConfiguredPrefills() {
1611
- const evalEngine = new ExpressionEvaluator({
1612
- responses: this.responses,
1613
- surveyContext: this.context
1614
- });
1615
516
  const maxPasses = Math.max(this.surveyDef.surveyItems.size, 1);
1616
517
  for (let pass = 0; pass < maxPasses; pass++) {
1617
- this.evalTemplateValues(evalEngine);
518
+ this.evalTemplateValues(this.createEvaluator());
1618
519
  let changedInPass = false;
1619
520
  this.surveyDef.surveyItems.forEach((item) => {
1620
521
  if (!item.isInteractive()) return;
1621
- changedInPass = this.applyConfiguredPrefillsForItem(item, evalEngine) || changedInPass;
522
+ changedInPass = this.applyConfiguredPrefillsForItem(item) || changedInPass;
1622
523
  });
1623
524
  if (!changedInPass) break;
1624
525
  }
1625
- this.evalTemplateValues(evalEngine);
526
+ this.evalTemplateValues(this.createEvaluator());
1626
527
  }
1627
- applyConfiguredPrefillsForItem(item, evalEngine) {
528
+ applyConfiguredPrefillsForItem(item) {
1628
529
  const responseItem = this.responses[item.id];
1629
530
  if (!responseItem) return false;
1630
531
  let changed = false;
1631
532
  for (const prefill of item.prefills ?? []) {
533
+ const evalEngine = this.createEvaluator();
1632
534
  const targetSlot = item.resolvePrefillTarget(prefill.target);
1633
535
  if (!targetSlot) continue;
1634
536
  if ((prefill.apply ?? SurveyItemPrefillApplyMode.ifEmpty) === SurveyItemPrefillApplyMode.ifEmpty && responseItem.response?.get(targetSlot.slotId) !== void 0) continue;
@@ -1639,8 +541,11 @@ var SurveyEngineCore = class {
1639
541
  if (!normalizedValue) continue;
1640
542
  const nextResponse = responseItem.response?.clone() ?? new ResponseItem();
1641
543
  nextResponse.setSlotValue(targetSlot.slotId, normalizedValue);
1642
- responseItem.response = nextResponse.isEmpty() ? void 0 : nextResponse;
1643
- changed = true;
544
+ const normalized = item.normalizeResponse(nextResponse);
545
+ this.responseDiagnostics.set(item.id, normalized.diagnostics ?? []);
546
+ const next = normalized.response?.isEmpty() ? void 0 : normalized.response;
547
+ if (JSON.stringify(responseItem.response?.serialize()) !== JSON.stringify(next?.serialize())) changed = true;
548
+ responseItem.response = next;
1644
549
  }
1645
550
  return changed;
1646
551
  }
@@ -1656,11 +561,9 @@ var SurveyEngineCore = class {
1656
561
  case "templateValue": return this.getTemplateValue(prefill.source.key);
1657
562
  case "previousResponse": {
1658
563
  if (!this.prefillEnvironment?.getPreviousResponse) return;
1659
- const refTarget = prefill.source.ref.target ?? prefill.target;
1660
564
  const resolved = this.prefillEnvironment.getPreviousResponse({
1661
565
  surveyKey: prefill.source.ref.surveyKey,
1662
- itemId: prefill.source.ref.itemId,
1663
- target: refTarget
566
+ slotId: prefill.source.ref.slotId
1664
567
  });
1665
568
  if (!resolved) return;
1666
569
  return resolved;
@@ -1726,6 +629,11 @@ var SurveyEngineCore = class {
1726
629
  };
1727
630
  }
1728
631
  renderGroup(groupDef, parent) {
632
+ if (!this.activeEvaluation().isItemVisible(groupDef.id)) return {
633
+ id: groupDef.id,
634
+ type: groupDef.type,
635
+ items: []
636
+ };
1729
637
  if (!parent) parent = {
1730
638
  id: groupDef.id,
1731
639
  type: "group",
@@ -1747,13 +655,48 @@ var SurveyEngineCore = class {
1747
655
  getResponseItem(itemId) {
1748
656
  return this.responses[itemId];
1749
657
  }
658
+ /** Resolve computed references against current answers and conditions, with cycle detection. */
659
+ getComputedResponseValue(slotId) {
660
+ return this.activeEvaluation().getAnswer(slotId);
661
+ }
662
+ /** Evaluate against the same current responses/context used by conditions and prefills. */
663
+ evaluateExpression(expression) {
664
+ return this.activeEvaluation().evaluator.eval(Expression.deserialize(expression));
665
+ }
666
+ activeEvaluation() {
667
+ return this.activeState ??= this.activeResponsePlan.evaluate(this.responses, this.context);
668
+ }
669
+ createEvaluator() {
670
+ return this.activeResponsePlan.evaluate(this.responses, this.context).evaluator;
671
+ }
672
+ /** Active answer used by conditions and scoring; hidden session values remain in getAnswer(). */
673
+ getActiveAnswer(slotId) {
674
+ return this.activeEvaluation().getAnswer(slotId);
675
+ }
676
+ /** Validate static runtime contracts without constructing a response session. */
677
+ static getConfigurationIssues(survey) {
678
+ return SurveyEngineCore.inspectConfiguration(survey).issues;
679
+ }
680
+ static inspectConfiguration(survey) {
681
+ const issues = [...survey.surveyItems.values()].flatMap((item) => item.getExecutionConfigurationIssues().map((issue) => `${(item.metadata?.itemLabel ?? "")?.trim() || item.id}: ${issue}`));
682
+ let plan;
683
+ try {
684
+ plan = new ActiveResponsePlan(survey);
685
+ } catch (error) {
686
+ issues.push(error instanceof Error ? error.message : String(error));
687
+ }
688
+ issues.push(...survey.getResponseSlotRegistry().diagnostics.map((diagnostic) => diagnostic.message));
689
+ issues.push(...survey.getBlockingExpressionIssues());
690
+ return {
691
+ issues,
692
+ plan
693
+ };
694
+ }
1750
695
  evalExpressions() {
1751
- const evalEngine = new ExpressionEvaluator({
1752
- responses: this.responses,
1753
- surveyContext: this.context
1754
- });
696
+ this.activeState = this.activeResponsePlan.evaluate(this.responses, this.context);
697
+ const evalEngine = this.activeState.evaluator;
1755
698
  this.evalTemplateValues(evalEngine);
1756
- this.evalDisplayConditions(evalEngine);
699
+ this.evalDisplayConditions();
1757
700
  this.evalDisableConditions(evalEngine);
1758
701
  this.evalValidations(evalEngine);
1759
702
  }
@@ -1778,34 +721,18 @@ var SurveyEngineCore = class {
1778
721
  }
1779
722
  evaluatedValue = {
1780
723
  type: ValueType.string,
1781
- value: format(/* @__PURE__ */ new Date(evaluatedValue.value * 1e3), templateValue.templateDef.dateFormat)
724
+ value: formatCalendarDate(evaluatedValue.value, templateValue.templateDef.dateFormat)
1782
725
  };
1783
- break;
1784
- default: break;
1785
726
  }
1786
727
  this.cache.templateValues[templateValueKey].value = evaluatedValue;
1787
728
  });
1788
729
  }
1789
- evalDisplayConditions(evalEngine) {
1790
- Object.keys(this.cache.displayConditions).forEach((itemKey) => {
1791
- const displayCondition = this.cache.displayConditions[itemKey];
1792
- if (displayCondition.root) {
1793
- const resolvedValue = evalEngine.eval(displayCondition.root.expression);
1794
- if (resolvedValue === void 0 || resolvedValue.type !== ValueType.boolean) {
1795
- console.warn("evalDisplayConditions: display condition expression returned undefined: " + itemKey);
1796
- return;
1797
- }
1798
- this.cache.displayConditions[itemKey].root.result = resolvedValue.value;
1799
- }
1800
- if (displayCondition.components) Object.keys(displayCondition.components).forEach((componentKey) => {
1801
- const resolvedValue = evalEngine.eval(displayCondition.components[componentKey].expression);
1802
- if (resolvedValue === void 0 || resolvedValue.type !== ValueType.boolean) {
1803
- console.warn("evalDisplayConditions: display condition component expression returned undefined: " + itemKey + "." + componentKey);
1804
- return;
1805
- }
1806
- this.cache.displayConditions[itemKey].components[componentKey].result = resolvedValue.value;
1807
- });
1808
- });
730
+ evalDisplayConditions() {
731
+ const active = this.activeEvaluation();
732
+ for (const [itemId, conditions] of Object.entries(this.cache.displayConditions)) {
733
+ if (conditions.root) conditions.root.result = active.isItemVisible(itemId);
734
+ for (const [id, condition] of Object.entries(conditions.components ?? {})) condition.result = active.isComponentVisible(itemId, id);
735
+ }
1809
736
  }
1810
737
  evalDisableConditions(evalEngine) {
1811
738
  Object.keys(this.cache.disabledConditions).forEach((itemKey) => {
@@ -1827,6 +754,7 @@ var SurveyEngineCore = class {
1827
754
  const resolvedValue = evalEngine.eval(validation[validationKey].expression);
1828
755
  if (resolvedValue === void 0 || resolvedValue.type !== ValueType.boolean) {
1829
756
  console.warn("evalValidations: validation expression returned undefined: " + itemId + "." + validationKey);
757
+ this.cache.validations[itemId][validationKey].result = false;
1830
758
  return;
1831
759
  }
1832
760
  this.cache.validations[itemId][validationKey].result = resolvedValue.value;
@@ -1845,6 +773,9 @@ const flattenTree = (itemTree) => {
1845
773
  //#endregion
1846
774
  //#region src/json-patch.ts
1847
775
  var JsonPatchError = class extends Error {
776
+ code;
777
+ path;
778
+ operationIndex;
1848
779
  constructor(message, code, path, operationIndex) {
1849
780
  super(message);
1850
781
  this.code = code;
@@ -1902,7 +833,8 @@ const getValueAtSegments = (document, segments, path) => {
1902
833
  return current;
1903
834
  };
1904
835
  const getValueAtJsonPointer = (document, pointer) => {
1905
- return getValueAtSegments(document, parseJsonPointer(pointer), pointer);
836
+ const segments = parseJsonPointer(pointer);
837
+ return getValueAtSegments(document, segments, pointer);
1906
838
  };
1907
839
  const deepEqualJson = (left, right) => {
1908
840
  if (left === right) return true;
@@ -1937,7 +869,12 @@ const applyAdd = (document, path, value) => {
1937
869
  return document;
1938
870
  }
1939
871
  if (!isRecord(parent)) throw new JsonPatchError(`JSON pointer "${path}" parent is not an object or array.`, "invalid-target-parent", path);
1940
- parent[key] = structuredCloneMethod(value);
872
+ Object.defineProperty(parent, key, {
873
+ configurable: true,
874
+ enumerable: true,
875
+ value: structuredCloneMethod(value),
876
+ writable: true
877
+ });
1941
878
  return document;
1942
879
  };
1943
880
  const applyRemove = (document, path) => {
@@ -2005,9 +942,11 @@ const applyJsonPatch = (document, operations) => {
2005
942
  patchedDocument = applyAdd(patchedDocument, operation.path, value);
2006
943
  break;
2007
944
  }
2008
- case "test":
2009
- if (!deepEqualJson(getValueAtJsonPointer(patchedDocument, operation.path), operation.value)) throw new JsonPatchError(`JSON Patch test failed at "${operation.path}".`, "test-failed", operation.path);
945
+ case "test": {
946
+ const value = getValueAtJsonPointer(patchedDocument, operation.path);
947
+ if (!deepEqualJson(value, operation.value)) throw new JsonPatchError(`JSON Patch test failed at "${operation.path}".`, "test-failed", operation.path);
2010
948
  break;
949
+ }
2011
950
  }
2012
951
  } catch (error) {
2013
952
  if (error instanceof JsonPatchError) throw new JsonPatchError(error.message, error.code, error.path, operationIndex);
@@ -2017,93 +956,13 @@ const applyJsonPatch = (document, operations) => {
2017
956
  return patchedDocument;
2018
957
  };
2019
958
  //#endregion
2020
- //#region src/response-exporter/types.ts
2021
- /** Meta column keys in fixed order (common attributes first). */
2022
- const META_COLUMN_ORDER = [
2023
- "submittedAt",
2024
- "participantId",
2025
- "responseId",
2026
- "versionId"
2027
- ];
2028
- /** Built-in transform modes for slot export. */
2029
- const SlotTransformMode = {
2030
- default: "default",
2031
- mask: "mask",
2032
- dateFormat: "dateFormat"
2033
- };
2034
- /** Separator between item key path and slot coding path in default export headers. */
2035
- const EXPORT_COLUMN_SLOT_SEPARATOR = "__";
2036
- /** Source of an export column. */
2037
- const ExportColumnSource = {
2038
- meta: "meta",
2039
- context: "context",
2040
- response: "response"
2041
- };
2042
- //#endregion
2043
- //#region src/response-exporter/slot-formatter.ts
2044
- /** Default transform config when no profile override. */
2045
- const DEFAULT_TRANSFORM = { mode: SlotTransformMode.default };
2046
- /**
2047
- * Export a single response slot to a string. Pure function for unit testing.
2048
- * Uses default formatting by ValueType unless overridden by transformConfig.
2049
- */
2050
- function exportSingleResponseSlot(params) {
2051
- const { value, valueType, transformConfig } = params;
2052
- if (value === void 0 || value === null) return "";
2053
- const mode = transformConfig.mode;
2054
- if (mode === SlotTransformMode.mask) return maskValue(value, transformConfig.maskChar ?? "***");
2055
- if (mode === SlotTransformMode.dateFormat && (valueType === ValueType.date || valueType === ValueType.dateArray)) return formatDateValue(value, transformConfig.dateFormat?.pattern ?? "yyyy-MM-dd");
2056
- return formatDefault(value);
2057
- }
2058
- const BooleanExportValues = {
2059
- TRUE: "TRUE",
2060
- FALSE: "FALSE"
2061
- };
2062
- function formatDefault(value) {
2063
- switch (value.type) {
2064
- case ValueType.string: return String(value.value);
2065
- case ValueType.reference: return String(value.value);
2066
- case ValueType.boolean: return value.value ? BooleanExportValues.TRUE : BooleanExportValues.FALSE;
2067
- case ValueType.number: return value.precision === NumberPrecision.int ? String(Math.floor(value.value)) : String(value.value);
2068
- case ValueType.date: return (/* @__PURE__ */ new Date(value.value * 1e3)).toISOString();
2069
- case ValueType.duration: {
2070
- const d = value;
2071
- return `${d.precision === NumberPrecision.int ? Math.floor(d.value) : d.value} ${d.unit}`;
2072
- }
2073
- case ValueType.stringArray: return value.value.join("; ");
2074
- case ValueType.referenceArray: return value.value.join("; ");
2075
- case ValueType.numberArray: {
2076
- const arr = value.value;
2077
- const prec = value.precision;
2078
- const fmt = (n) => prec === NumberPrecision.int ? Math.floor(n) : n;
2079
- return arr.map(fmt).join("; ");
2080
- }
2081
- case ValueType.dateArray: return value.value.map((ts) => (/* @__PURE__ */ new Date(ts * 1e3)).toISOString()).join("; ");
2082
- case ValueType.durationArray: {
2083
- const d = value;
2084
- const prec = d.precision === NumberPrecision.int;
2085
- const fmt = (n) => prec ? Math.floor(n) : n;
2086
- return d.value.map((v) => `${fmt(v)} ${d.unit}`).join("; ");
2087
- }
2088
- default: return String(value.value ?? "");
2089
- }
2090
- }
2091
- function formatDateValue(value, pattern) {
2092
- if (value.type === ValueType.date) return format(/* @__PURE__ */ new Date(value.value * 1e3), pattern);
2093
- if (value.type === ValueType.dateArray) return value.value.map((ts) => format(/* @__PURE__ */ new Date(ts * 1e3), pattern)).join("; ");
2094
- return formatDefault(value);
2095
- }
2096
- function maskValue(value, maskChar) {
2097
- return valueToString(value) ? maskChar : "";
2098
- }
2099
- function valueToString(value) {
2100
- switch (value.type) {
2101
- case ValueType.string:
2102
- case ValueType.reference: return String(value.value);
2103
- case ValueType.stringArray:
2104
- case ValueType.referenceArray: return value.value.join(" ");
2105
- default: return String(value.value ?? "");
2106
- }
959
+ //#region src/response-exporter/validate-configuration.ts
960
+ /** Allow incomplete authoring drafts, but never export invalid item coding paths. */
961
+ function validateExportConfiguration(survey, registry = survey.getResponseSlotRegistry()) {
962
+ const issues = [...survey.surveyItems.values()].flatMap((item) => item.getExecutionConfigurationIssues().map((issue) => `${(item.metadata?.itemLabel ?? "")?.trim() || item.id}: ${issue}`));
963
+ issues.push(...registry.diagnostics.map((diagnostic) => diagnostic.message));
964
+ issues.push(...survey.getBlockingExpressionIssues());
965
+ if (issues.length) throw new Error(issues.join("\n"));
2107
966
  }
2108
967
  //#endregion
2109
968
  //#region src/response-exporter/csv-serializer.ts
@@ -2141,181 +1000,226 @@ function serializeToCsv(columns, rows, options) {
2141
1000
  }
2142
1001
  //#endregion
2143
1002
  //#region src/response-exporter/exporter.ts
2144
- function slotOverrideKey(itemId, slotId) {
2145
- return `${itemId}::${slotId}`;
2146
- }
2147
- function responseColumnId(itemId, slotId) {
2148
- return `${itemId}::${slotId}`;
2149
- }
2150
- function metaColumnId(metaField) {
2151
- return `meta:${metaField}`;
2152
- }
2153
- function contextColumnId(contextKey) {
2154
- return `context:${contextKey}`;
2155
- }
2156
- function formatMetaValue(response, metaField) {
2157
- switch (metaField) {
2158
- case "submittedAt":
2159
- if (response.submittedAt == null || response.submittedAt === 0) return "";
2160
- return format(/* @__PURE__ */ new Date(response.submittedAt * 1e3), "yyyy-MM-dd'T'HH:mm:ss.SSSxxx");
2161
- case "participantId": return response.participantId ?? "";
2162
- case "responseId": return response.key;
2163
- case "versionId": return response.versionId;
2164
- default: return "";
1003
+ const canonical = (value) => JSON.stringify(value, (_key, entry) => entry && typeof entry === "object" && !Array.isArray(entry) ? Object.fromEntries(Object.entries(entry).sort(([a], [b]) => a.localeCompare(b))) : entry);
1004
+ /** Export only against schemas selected by the host, never against response version tags. */
1005
+ var SurveyResponseExporter = class SurveyResponseExporter {
1006
+ batches;
1007
+ options;
1008
+ constructor(batches, options = {}) {
1009
+ this.batches = batches;
1010
+ this.options = options;
1011
+ if (!batches.length) throw new Error("Supply at least one schema-bound export batch");
1012
+ if (batches.some((batch) => !batch.schema || !Array.isArray(batch.responses))) throw new Error("Each export batch requires a schema and responses array");
1013
+ if (batches.length > 1 && !options.codingPolicy) throw new Error("Combined export requires an explicit original or harmonized coding policy");
1014
+ }
1015
+ batch(index) {
1016
+ const batch = this.batches[index];
1017
+ if (!batch) throw new Error(`Unknown export batch: ${index}`);
1018
+ return batch;
1019
+ }
1020
+ profile(batch) {
1021
+ if (!this.options.profile) return batch.profile;
1022
+ return {
1023
+ ...batch.profile,
1024
+ ...this.options.profile,
1025
+ slotOverrides: {
1026
+ ...batch.profile?.slotOverrides,
1027
+ ...this.options.profile.slotOverrides
1028
+ },
1029
+ columnHeaders: {
1030
+ ...batch.profile?.columnHeaders,
1031
+ ...this.options.profile.columnHeaders
1032
+ },
1033
+ contextOverrides: {
1034
+ ...batch.profile?.contextOverrides,
1035
+ ...this.options.profile.contextOverrides
1036
+ }
1037
+ };
2165
1038
  }
2166
- }
2167
- var SurveyResponseExporter = class {
2168
- versions = /* @__PURE__ */ new Map();
2169
- constructor(versionBindings, profiles = []) {
2170
- this.profiles = profiles;
2171
- for (const vb of versionBindings) this.versions.set(vb.versionId, vb);
2172
- }
2173
- getSurveyForVersion(versionId) {
2174
- return this.versions.get(versionId);
2175
- }
2176
- /**
2177
- * Compute response-only columns from survey definition.
2178
- */
2179
- computeResponseColumns(versionId) {
2180
- const binding = this.versions.get(versionId);
2181
- if (!binding) return [];
2182
- const survey = binding.survey;
2183
- const keyIndex = survey.keyIndex;
2184
- const keyByItemId = new Map(keyIndex.map((r) => [r.itemId, r]));
2185
- const profile = this.resolveProfileForVersion(versionId);
2186
- const columns = [];
2187
- for (const { itemId, slots } of survey.getResponseSlotDefinitions()) {
2188
- const keyInfo = keyByItemId.get(itemId);
2189
- const itemBaseKey = keyInfo ? [...keyInfo.keyPath, keyInfo.key].join(".") : itemId;
2190
- for (const slot of slots) {
2191
- const columnId = responseColumnId(itemId, slot.slotId);
2192
- const codingPath = [...slot.keyPath, slot.key].filter((part) => part.trim().length > 0);
2193
- const defaultHeader = codingPath.length > 0 ? `${itemBaseKey}__${codingPath.join(".")}` : `${itemBaseKey}__${slot.slotId}`;
2194
- const header = this.resolveColumnHeader(profile, columnId, defaultHeader);
2195
- columns.push({
2196
- id: columnId,
2197
- header,
2198
- source: ExportColumnSource.response,
2199
- itemId,
2200
- slotId: slot.slotId,
2201
- valueType: slot.valueType
2202
- });
1039
+ compileContexts() {
1040
+ return new Map(this.batches.map((batch) => {
1041
+ const registry = batch.schema.getResponseSlotRegistry();
1042
+ validateExportConfiguration(batch.schema, registry);
1043
+ const slots = new Map(registry.definitions.map((slot) => [slot.slotId, slot]));
1044
+ const profile = this.profile(batch);
1045
+ const transforms = new Map([...slots.values()].map((slot) => [slot.slotId, {
1046
+ ...DEFAULT_TRANSFORM,
1047
+ ...slot.exportSettings,
1048
+ ...profile?.slotOverrides?.[slot.slotId]
1049
+ }]));
1050
+ const columns = [...slots.values()].flatMap((slot) => projectSlot(slot, transforms.get(slot.slotId)).map((column) => ({
1051
+ ...column,
1052
+ header: profile?.columnHeaders?.[column.id]?.trim() ?? column.header
1053
+ })));
1054
+ const encodings = /* @__PURE__ */ new Map();
1055
+ for (const slotId of new Set(columns.map((column) => column.slotId))) {
1056
+ const { include: _include, ...encoding } = resolvedSlotEncoding(slots.get(slotId), transforms.get(slotId));
1057
+ encodings.set(slotId, canonical(encoding));
2203
1058
  }
2204
- }
2205
- return columns;
2206
- }
2207
- /**
2208
- * Compute full export columns: meta first, then context (from responses), then response columns.
2209
- * Column order: meta (fixed) -> context (profile order or alphabetical) -> response (profile order + alphabetical).
2210
- */
2211
- computeColumns(versionId, responses, versionIds) {
2212
- const vids = versionIds ?? [versionId];
2213
- const responseCols = [];
2214
- const seen = /* @__PURE__ */ new Set();
2215
- for (const vid of vids) for (const c of this.computeResponseColumns(vid)) if (!seen.has(c.id)) {
2216
- seen.add(c.id);
2217
- responseCols.push(c);
2218
- }
2219
- const profile = this.resolveProfileForVersion(versionId);
2220
- const metaColumns = META_COLUMN_ORDER.map((metaField) => ({
2221
- id: metaColumnId(metaField),
2222
- header: this.resolveColumnHeader(profile, metaColumnId(metaField), metaField),
2223
- source: ExportColumnSource.meta,
2224
- metaField
2225
- }));
2226
- const contextKeys = /* @__PURE__ */ new Set();
2227
- for (const r of responses ?? []) if (r.context) for (const k of Object.keys(r.context)) contextKeys.add(k);
2228
- const contextOrder = profile?.contextColumnOrder;
2229
- const contextCols = Array.from(contextKeys).sort(contextOrder ? (a, b) => {
2230
- const ia = contextOrder.indexOf(a);
2231
- const ib = contextOrder.indexOf(b);
2232
- if (ia >= 0 && ib >= 0) return ia - ib;
2233
- if (ia >= 0) return -1;
2234
- if (ib >= 0) return 1;
2235
- return a.localeCompare(b);
2236
- } : void 0).map((contextKey) => ({
2237
- id: contextColumnId(contextKey),
2238
- header: this.resolveColumnHeader(profile, contextColumnId(contextKey), `context.${contextKey}`),
2239
- source: ExportColumnSource.context,
2240
- contextKey
1059
+ return [batch, {
1060
+ registry,
1061
+ slots,
1062
+ profile,
1063
+ transforms,
1064
+ encodings,
1065
+ columns,
1066
+ included: new Set(columns.map((column) => column.id)),
1067
+ activePlan: columns.some((column) => slots.get(column.slotId)?.computed) ? new ActiveResponsePlan(batch.schema) : void 0
1068
+ }];
2241
1069
  }));
2242
- const respOrder = profile?.responseColumnOrder ?? [];
2243
- const byOrder = respOrder.filter((id) => responseCols.some((c) => c.id === id)).map((id) => responseCols.find((c) => c.id === id));
2244
- const remaining = responseCols.filter((c) => !respOrder.includes(c.id)).sort((a, b) => a.header.localeCompare(b.header));
2245
- const respCols = [...byOrder, ...remaining];
2246
- const columns = [
2247
- ...metaColumns,
2248
- ...contextCols,
2249
- ...respCols
2250
- ];
2251
- this.assertUniqueHeaders(columns);
2252
- return columns;
2253
- }
2254
- resolveProfileForVersion(versionId) {
2255
- return this.profiles.find((p) => !p.versionId || p.versionId === versionId);
2256
- }
2257
- resolveColumnHeader(profile, columnId, defaultHeader) {
2258
- const override = profile?.columnHeaders?.[columnId]?.trim();
2259
- return override && override.length > 0 ? override : defaultHeader;
2260
- }
2261
- assertUniqueHeaders(columns) {
2262
- const seen = /* @__PURE__ */ new Map();
2263
- for (const column of columns) {
2264
- const existingId = seen.get(column.header);
2265
- if (existingId && existingId !== column.id) throw new Error(`Duplicate export column header '${column.header}' for columns '${existingId}' and '${column.id}'`);
2266
- seen.set(column.header, column.id);
2267
- }
2268
1070
  }
2269
- getTransformConfig(versionId, itemId, slotId) {
2270
- return this.resolveProfileForVersion(versionId)?.slotOverrides?.[slotOverrideKey(itemId, slotId)] ?? DEFAULT_TRANSFORM;
2271
- }
2272
- getContextTransformConfig(versionId, contextKey) {
2273
- return this.resolveProfileForVersion(versionId)?.contextOverrides?.[contextKey] ?? DEFAULT_TRANSFORM;
2274
- }
2275
- exportResponse(response) {
2276
- if (!this.versions.get(response.versionId)) return null;
2277
- const columns = this.computeColumns(response.versionId, [response]);
2278
- const row = {};
2279
- for (const col of columns) {
2280
- if (col.source === ExportColumnSource.meta && col.metaField) {
2281
- row[col.header] = formatMetaValue(response, col.metaField);
2282
- continue;
2283
- }
2284
- if (col.source === ExportColumnSource.context && col.contextKey) {
2285
- const value = response.context?.[col.contextKey];
2286
- const transformConfig = this.getContextTransformConfig(response.versionId, col.contextKey);
2287
- const valueType = value?.type ?? ValueType.string;
2288
- row[col.header] = exportSingleResponseSlot({
2289
- value,
2290
- valueType,
2291
- transformConfig
1071
+ validateProfile(profile, batches, contexts, extraResponses = []) {
1072
+ if (!profile) return;
1073
+ if (profile.schemaVersion !== 1) throw new Error(`Unsupported exporter profile schema version: ${String(profile.schemaVersion)}`);
1074
+ const slots = new Map(batches.flatMap((batch) => [...contexts.get(batch).slots]));
1075
+ const contextKeys = new Set([...batches.flatMap((batch) => batch.responses), ...extraResponses].flatMap((response) => Object.keys(response.context ?? {})));
1076
+ const columns = /* @__PURE__ */ new Set([
1077
+ ...[...slots.values()].flatMap((slot) => projectSlot(slot, { mode: "default" }, true).map((column) => column.id)),
1078
+ ...META_COLUMN_ORDER.map((field) => encodeExportColumnId(["meta", field])),
1079
+ ...[...contextKeys].map((key) => encodeExportColumnId(["context", key]))
1080
+ ]);
1081
+ for (const id of Object.keys(profile.slotOverrides ?? {})) if (!slots.has(id)) throw new Error(`Profile references unknown response slot "${id}"`);
1082
+ for (const id of [...Object.keys(profile.columnHeaders ?? {}), ...profile.responseColumnOrder ?? []]) if (!columns.has(id)) throw new Error(`Profile references unknown export column "${id}"`);
1083
+ for (const id of profile.responseColumnOrder ?? []) if (![...slots.values()].some((slot) => projectSlot(slot, { mode: "default" }, true).some((column) => column.id === id))) throw new Error(`Response column order contains a non-response column: ${id}`);
1084
+ for (const key of [...Object.keys(profile.contextOverrides ?? {}), ...profile.contextColumnOrder ?? []]) if (!contextKeys.has(key)) throw new Error(`Profile references unknown context key "${key}"`);
1085
+ for (const slot of slots.values()) if ((slot.naming?.kind === "matrix" || slot.rowId || slot.columnId) && projectSlot(slot, { mode: "default" }, true).some((column) => Object.hasOwn(profile.columnHeaders ?? {}, column.id))) throw new Error(`Matrix variable "${slot.slotId}" cannot have an individual header override; edit its matrix coding components`);
1086
+ for (const [id, header] of Object.entries(profile.columnHeaders ?? {})) if (typeof header !== "string" || !header.trim()) throw new Error(`Export header for "${id}" cannot be empty`);
1087
+ if (new Set(profile.responseColumnOrder).size !== (profile.responseColumnOrder?.length ?? 0)) throw new Error("Response column order contains duplicate identities");
1088
+ }
1089
+ computeResponseColumns(batchIndex = 0, extraResponses = []) {
1090
+ const batch = this.batch(batchIndex);
1091
+ const contexts = this.compileContexts();
1092
+ this.validateProfile(batch.profile, [batch], contexts, extraResponses);
1093
+ this.validateProfile(this.options.profile, this.batches, contexts, extraResponses);
1094
+ return contexts.get(batch).columns;
1095
+ }
1096
+ computeColumns(extraResponses = []) {
1097
+ return this.columns(this.compileContexts(), extraResponses);
1098
+ }
1099
+ columns(contexts, extraResponses = []) {
1100
+ this.validateProfile(this.options.profile, this.batches, contexts, extraResponses);
1101
+ const columns = /* @__PURE__ */ new Map();
1102
+ const signatures = /* @__PURE__ */ new Map();
1103
+ const encodings = /* @__PURE__ */ new Map();
1104
+ this.batches.forEach((batch) => {
1105
+ const context = contexts.get(batch);
1106
+ this.validateProfile(batch.profile, [batch], contexts, extraResponses);
1107
+ for (const slot of context.slots.values()) {
1108
+ const signature = canonical({
1109
+ type: slot.valueType,
1110
+ allowedValues: slot.allowedValues?.map((value) => canonical(value)).sort(),
1111
+ codes: this.options.codingPolicy === "harmonized" ? context.profile?.slotOverrides?.[slot.slotId]?.optionCodes ?? slot.referenceValueKeys : slot.referenceValueKeys,
1112
+ computed: slot.computed,
1113
+ scores: slot.referenceValueScores
2292
1114
  });
2293
- continue;
1115
+ const previous = signatures.get(slot.slotId);
1116
+ if (previous && previous !== signature) throw new Error(`Incompatible schema definitions for slot "${slot.slotId}"; supply a compatible explicit harmonization mapping`);
1117
+ signatures.set(slot.slotId, signature);
2294
1118
  }
2295
- if (col.source === ExportColumnSource.response && col.itemId != null && col.slotId != null && col.valueType != null) {
2296
- const value = response.responses.get(col.itemId)?.response?.get(col.slotId);
2297
- const transformConfig = this.getTransformConfig(response.versionId, col.itemId, col.slotId);
2298
- row[col.header] = exportSingleResponseSlot({
2299
- value,
2300
- valueType: col.valueType,
2301
- transformConfig
2302
- });
1119
+ for (const column of context.columns) {
1120
+ const signature = context.encodings.get(column.slotId);
1121
+ if (encodings.has(column.id) && encodings.get(column.id) !== signature) throw new Error(`Incompatible export encodings for column "${column.id}"; supply one harmonized profile`);
1122
+ encodings.set(column.id, signature);
1123
+ const existing = columns.get(column.id);
1124
+ if (existing && existing.header !== column.header) throw new Error(`Conflicting names for column "${column.id}"; select an explicit harmonized output header`);
1125
+ columns.set(column.id, column);
2303
1126
  }
1127
+ });
1128
+ const profile = this.options.profile ?? (this.batches.length === 1 ? this.batches[0].profile : void 0);
1129
+ const allResponses = [...this.batches.flatMap((batch) => batch.responses), ...extraResponses];
1130
+ const contextKeys = [...new Set(allResponses.flatMap((response) => Object.keys(response.context ?? {})))];
1131
+ const ordered = (values, order, id, name) => values.sort((a, b) => {
1132
+ const left = order.indexOf(id(a)), right = order.indexOf(id(b));
1133
+ if (left >= 0 || right >= 0) return (left < 0 ? Infinity : left) - (right < 0 ? Infinity : right);
1134
+ return name(a).localeCompare(name(b));
1135
+ });
1136
+ const result = [
1137
+ ...META_COLUMN_ORDER.map((field) => ({
1138
+ id: encodeExportColumnId(["meta", field]),
1139
+ header: profile?.columnHeaders?.[encodeExportColumnId(["meta", field])]?.trim() ?? field,
1140
+ source: ExportColumnSource.meta,
1141
+ metaField: field
1142
+ })),
1143
+ ...ordered(contextKeys, profile?.contextColumnOrder ?? [], (value) => value, (value) => value).map((key) => ({
1144
+ id: encodeExportColumnId(["context", key]),
1145
+ header: profile?.columnHeaders?.[encodeExportColumnId(["context", key])]?.trim() ?? `context.${key}`,
1146
+ source: ExportColumnSource.context,
1147
+ contextKey: key
1148
+ })),
1149
+ ...ordered([...columns.values()], profile?.responseColumnOrder ?? [], (value) => value.id, (value) => value.header)
1150
+ ];
1151
+ const headers = /* @__PURE__ */ new Set();
1152
+ for (const column of result) {
1153
+ if (headers.has(column.header)) throw new Error(`Duplicate export column header '${column.header}'`);
1154
+ headers.add(column.header);
1155
+ }
1156
+ return result;
1157
+ }
1158
+ row(response, batch, columns, context) {
1159
+ const { registry, slots, profile, included, transforms } = context;
1160
+ const diagnostics = registry.validateAnswers(response.answers);
1161
+ if (diagnostics.length) throw new Error(diagnostics.map((issue) => issue.message).join("\n"));
1162
+ const row = Object.create(null);
1163
+ const recorded = Object.create(null);
1164
+ if (context.activePlan) for (const [slotId, value] of Object.entries(response.answers)) {
1165
+ const slot = slots.get(slotId);
1166
+ const item = batch.schema.surveyItems.get(slot.itemId);
1167
+ (recorded[slot.itemId] ??= new SurveyItemResponse({
1168
+ itemId: slot.itemId,
1169
+ itemType: item.type
1170
+ }, new ResponseItem())).response.setSlotValue(slotId, structuredClone(value));
1171
+ }
1172
+ const active = context.activePlan?.evaluate(recorded, {
1173
+ locale: response.evaluationContext?.locale ?? batch.schema.locales[0] ?? "en",
1174
+ participantFlags: response.evaluationContext?.participantFlags,
1175
+ customValues: response.context
1176
+ });
1177
+ for (const column of columns) if (column.source === ExportColumnSource.meta) switch (column.metaField) {
1178
+ case "responseId":
1179
+ row[column.header] = response.key;
1180
+ break;
1181
+ case "participantId":
1182
+ row[column.header] = response.participantId ?? "";
1183
+ break;
1184
+ case "sourceId":
1185
+ row[column.header] = batch.sourceId ?? "";
1186
+ break;
1187
+ case "submittedAt": row[column.header] = response.submittedAt ? format(/* @__PURE__ */ new Date(response.submittedAt * 1e3), "yyyy-MM-dd'T'HH:mm:ss.SSSxxx") : "";
1188
+ }
1189
+ else if (column.source === ExportColumnSource.context) {
1190
+ const value = response.context?.[column.contextKey];
1191
+ row[column.header] = exportSingleResponseSlot({
1192
+ value,
1193
+ valueType: value?.type ?? ValueType.string,
1194
+ transformConfig: profile?.contextOverrides?.[column.contextKey] ?? {
1195
+ ...DEFAULT_TRANSFORM,
1196
+ categorical: "ids"
1197
+ }
1198
+ });
1199
+ } else {
1200
+ const slot = slots.get(column.slotId);
1201
+ row[column.header] = slot && included.has(column.id) ? formatProjectedValue(slot, column, slot.computed ? active?.getAnswer(slot.slotId) : response.answers[slot.slotId], transforms.get(slot.slotId)) : "";
2304
1202
  }
2305
1203
  return row;
2306
1204
  }
2307
- /**
2308
- * Compute columns for a batch of responses (union of response columns across versions).
2309
- */
2310
- computeColumnsForBatch(responses) {
2311
- if (responses.length === 0) return [];
2312
- const versionIds = [...new Set(responses.map((r) => r.versionId))];
2313
- const primaryVersionId = versionIds[0];
2314
- return this.computeColumns(primaryVersionId, responses, versionIds);
1205
+ /** Explicitly associate one response with a supplied batch schema. */
1206
+ exportResponse(response, batchIndex = 0) {
1207
+ return new SurveyResponseExporter([{
1208
+ ...this.batch(batchIndex),
1209
+ responses: [response]
1210
+ }], { profile: this.options.profile }).exportRows()[0];
1211
+ }
1212
+ exportRows() {
1213
+ const contexts = this.compileContexts();
1214
+ return this.rows(this.columns(contexts), contexts);
2315
1215
  }
2316
- exportResponsesToCsv(responses, options) {
2317
- if (responses.length === 0) return "";
2318
- return serializeToCsv(this.computeColumnsForBatch(responses), responses.map((r) => this.exportResponse(r)).filter((r) => r !== null), options);
1216
+ rows(columns, contexts) {
1217
+ return this.batches.flatMap((batch) => batch.responses.map((response) => this.row(response, batch, columns, contexts.get(batch))));
1218
+ }
1219
+ exportResponsesToCsv(options) {
1220
+ const contexts = this.compileContexts();
1221
+ const columns = this.columns(contexts);
1222
+ return serializeToCsv(columns, this.rows(columns, contexts), options);
2319
1223
  }
2320
1224
  };
2321
1225
  //#endregion
@@ -2324,20 +1228,35 @@ var SurveyResponseExporter = class {
2324
1228
  * Generate a structured codebook from a survey definition.
2325
1229
  */
2326
1230
  function generateCodebook(survey, options) {
1231
+ const registry = survey.getResponseSlotRegistry();
1232
+ validateExportConfiguration(survey, registry);
1233
+ const slots = registry.definitions;
2327
1234
  const locales = options?.locales ?? survey.locales;
2328
- const keyIndex = survey.keyIndex;
2329
- const keyByItemId = new Map(keyIndex.map((r) => [r.itemId, r]));
2330
- const slotDefsByItemId = new Map(survey.getResponseSlotDefinitions().map(({ itemId, slots }) => [itemId, slots.map((slot) => ({
2331
- slotId: slot.slotId,
2332
- key: slot.key,
2333
- keyPath: [...slot.keyPath],
2334
- valueType: slot.valueType
2335
- }))]));
1235
+ const slotDefsByItemId = /* @__PURE__ */ new Map();
1236
+ for (const slot of slots) {
1237
+ const entries = slotDefsByItemId.get(slot.itemId) ?? [];
1238
+ entries.push({
1239
+ slotId: slot.slotId,
1240
+ primitiveId: slot.primitiveId,
1241
+ ...slot.rowId ? { rowId: slot.rowId } : {},
1242
+ ...slot.columnId ? { columnId: slot.columnId } : {},
1243
+ variableName: slot.variableName,
1244
+ valueType: slot.valueType,
1245
+ encoding: resolvedSlotEncoding(slot, {
1246
+ mode: "default",
1247
+ ...slot.exportSettings,
1248
+ ...options?.profile?.slotOverrides?.[slot.slotId]
1249
+ }),
1250
+ optionLabels: slot.referenceValueLabels,
1251
+ allowedValues: slot.allowedValues,
1252
+ ...slot.computed ? { computed: structuredClone(slot.computed) } : {},
1253
+ ...slot.referenceValueKeys ? { referenceValueKeys: { ...slot.referenceValueKeys } } : {},
1254
+ ...slot.referenceValueScores ? { referenceValueScores: { ...slot.referenceValueScores } } : {}
1255
+ });
1256
+ slotDefsByItemId.set(slot.itemId, entries);
1257
+ }
2336
1258
  const items = [];
2337
1259
  for (const item of survey.surveyItems.values()) {
2338
- const keyInfo = keyByItemId.get(item.id);
2339
- const key = keyInfo?.key ?? item.id;
2340
- const keyPath = keyInfo?.keyPath ?? [];
2341
1260
  const slotList = slotDefsByItemId.get(item.id) ?? [];
2342
1261
  slotList.sort((a, b) => a.slotId.localeCompare(b.slotId));
2343
1262
  let contentByLocale;
@@ -2355,8 +1274,8 @@ function generateCodebook(survey, options) {
2355
1274
  }
2356
1275
  items.push({
2357
1276
  id: item.id,
2358
- key,
2359
- keyPath,
1277
+ editorName: survey.resolveItemEditorName(item.id, locales[0]),
1278
+ breadcrumb: survey.getItemDisplayPath(item.id, locales[0]).breadcrumb,
2360
1279
  type: item.type,
2361
1280
  interactive: item.isInteractive(),
2362
1281
  slots: slotList,
@@ -2365,10 +1284,41 @@ function generateCodebook(survey, options) {
2365
1284
  }
2366
1285
  return {
2367
1286
  surveyKey: survey.surveyKey,
2368
- items: items.sort((a, b) => a.key.localeCompare(b.key))
1287
+ items,
1288
+ projection: new SurveyResponseExporter([{
1289
+ schema: survey,
1290
+ responses: [],
1291
+ profile: options?.profile
1292
+ }]).computeColumns(),
1293
+ profile: options?.profile && structuredClone(options.profile),
1294
+ missingValue: ""
1295
+ };
1296
+ }
1297
+ /** Describe precisely the combined projection and each host-selected source definition. */
1298
+ function generateExportCodebook(batches, options = {}) {
1299
+ const exporter = new SurveyResponseExporter(batches, options);
1300
+ return {
1301
+ schemaVersion: 1,
1302
+ codingPolicy: options.codingPolicy ?? "original",
1303
+ columns: exporter.computeColumns(),
1304
+ missingValue: "",
1305
+ profile: options.profile && structuredClone(options.profile),
1306
+ sources: batches.map((batch) => ({
1307
+ sourceId: batch.sourceId,
1308
+ surveySchemaVersion: batch.schema.serialize().schemaVersion,
1309
+ profile: batch.profile && structuredClone(batch.profile),
1310
+ variables: batch.schema.getResponseSlotRegistry().definitions.map((slot) => ({
1311
+ ...slot,
1312
+ encoding: resolvedSlotEncoding(slot, {
1313
+ mode: "default",
1314
+ ...slot.exportSettings,
1315
+ ...options.profile?.slotOverrides?.[slot.slotId] ?? batch.profile?.slotOverrides?.[slot.slotId]
1316
+ })
1317
+ }))
1318
+ }))
2369
1319
  };
2370
1320
  }
2371
1321
  //#endregion
2372
- export { AndExpressionEditor, CURRENT_SURVEY_SCHEMA, ConstBooleanEditor, ConstDateArrayEditor, ConstDateEditor, ConstExpression, ConstNumberArrayEditor, ConstNumberEditor, ConstStringArrayEditor, ConstStringEditor, ContentType, ContextVariableExpression, ContextVariableType, CtxCustomExpressionEditor, CtxCustomValueEditor, CtxLocaleEditor, CtxPFlagDateEditor, CtxPFlagIsDefinedEditor, CtxPFlagNumEditor, CtxPFlagStringEditor, DEFAULT_TRANSFORM, DurationUnits, EXPORT_COLUMN_SLOT_SEPARATOR, EqExpressionEditor, Expression, ExpressionEditor, ExpressionEvaluator, ExpressionType, FunctionExpression, FunctionExpressionNames, GroupItemCore, GtExpressionEditor, GteExpressionEditor, InRangeExpressionEditor, JsonPatchError, LtExpressionEditor, LteExpressionEditor, META_COLUMN_ORDER, MaxExpressionEditor, MinExpressionEditor, NumberPrecision, OrExpressionEditor, PageBreakItemCore, ReferenceUsageType, ReservedSurveyItemTypes, ResponseItem, ResponseVariableEditor, ResponseVariableExpression, SURVEY_EDITOR_ITEM_COLORS, SURVEY_RESPONSE_SCHEMA_VERSION, SlotTransformMode, StrEqExpressionEditor, StrListContainsExpressionEditor, SumExpressionEditor, Survey, SurveyEngineCore, SurveyEventTypes, SurveyItemCore, SurveyItemKey, SurveyItemPrefillApplyMode, SurveyItemPrefillTargetType, SurveyItemResponse, SurveyItemTranslations, SurveyResponse, SurveyResponseExporter, SurveyTranslations, TemplateDefTypes, ValueReference, ValueReferenceMethod, ValueType, analyzeSurveyExpressionReferences, and, applyJsonPatch, assertResponseValue, buildItemExpression, builtInItemCoreRegistry, collectSurveyExpressionLocations, const_boolean, const_date, const_date_array, const_number, const_number_array, const_string, const_string_array, createFullRegistry, createItemCore, createItemTypeDefinitionRegistry, createRichTextContent, createSeededRandom, ctx_custom_expression, ctx_custom_value, ctx_locale, ctx_pflag_date, ctx_pflag_is_defined, ctx_pflag_num, ctx_pflag_string, decodeJsonPointerSegment, deserializeSurveyItemPrefill, deserializeTemplateValue, deserializeTemplateValues, encodeJsonPointerSegment, eq, escapeCsvCell, exportSingleResponseSlot, expressionReferenceDiagnosticKey, findIntroducedExpressionReferenceDiagnostics, flattenTree, generateCodebook, generateCodingKey, generateId, getAssetUsagesFromContent, getContentPlainText, getItemExpressionDefinition, getPlainTextFromRichTextContent, getValueAtJsonPointer, gt, gte, hasRenderableRichTextBlock, hasRenderableRichTextContent, in_range, initValueForType, isBuiltInItemType, isContentEmpty, isLegacyItemGroupComponent, isLegacySurveyGroupItem, isResponseValue, lt, lte, max, min, or, parseJsonPointer, prefillTargetsEqual, replaceResponseVariableReferences, responseValueReferenceIds, response_boolean, response_date, response_date_array, response_number, response_number_array, response_string, response_string_array, serializeSurveyItemPrefill, serializeTemplateValue, serializeTemplateValues, serializeToCsv, shuffleArray, shuffleIndices, str_eq, str_list_contains, structuredCloneMethod, sum, toItemTypeDefinitionRegistry, validateLocale };
1322
+ export { ActiveResponsePlan, AndExpressionEditor, CURRENT_SURVEY_SCHEMA, CURRENT_SURVEY_SCHEMA_VERSION, ConstBooleanEditor, ConstDateArrayEditor, ConstDateEditor, ConstExpression, ConstNumberArrayEditor, ConstNumberEditor, ConstStringArrayEditor, ConstStringEditor, ContentType, ContextVariableExpression, ContextVariableType, CtxCustomExpressionEditor, CtxCustomValueEditor, CtxLocaleEditor, CtxPFlagDateEditor, CtxPFlagIsDefinedEditor, CtxPFlagNumEditor, CtxPFlagStringEditor, DATE_FUNCTION_SIGNATURES, DEFAULT_TRANSFORM, DurationUnits, EXPORTER_PROFILE_SCHEMA_VERSION, EqExpressionEditor, Expression, ExpressionEditor, ExpressionEvaluator, ExpressionType, FunctionExpression, FunctionExpressionNames, GroupItemCore, GtExpressionEditor, GteExpressionEditor, InRangeExpressionEditor, JsonPatchError, LtExpressionEditor, LteExpressionEditor, META_COLUMN_ORDER, MaxExpressionEditor, MinExpressionEditor, NumberPrecision, OrExpressionEditor, PageBreakItemCore, RESPONSE_VARIABLE_NAME_MAX_LENGTH, ReferenceUsageType, ReservedSurveyItemTypes, ResponseItem, ResponseSlotRegistry, ResponseVariableEditor, ResponseVariableExpression, SURVEY_EDITOR_ITEM_COLORS, SURVEY_EXPRESSION_REFERENCE_DIAGNOSTIC_POLICY, SURVEY_RESPONSE_SCHEMA_VERSION, SlotTransformMode, StrEqExpressionEditor, StrListContainsExpressionEditor, SumExpressionEditor, Survey, SurveyEngineCore, SurveyEventTypes, SurveyItemCore, SurveyItemPrefillApplyMode, SurveyItemPrefillTargetType, SurveyItemResponse, SurveyItemTranslations, SurveyResponse, SurveyResponseExporter, SurveyTranslations, TemplateDefTypes, ValueReference, ValueReferenceMethod, ValueType, addCalendarDate, allocateResponseVariableName, analyzeSurveyExpressionReferences, and, applyJsonPatch, assertResponseValue, buildItemExpression, builtInItemCoreRegistry, calendarDateBounds, calendarDateFromLocalDate, calendarDatePrecision, calendarDateToLocalDate, collectConfigValueExpressions, collectExpressionResponseReferences, collectSurveyExpressionLocations, compareCalendarDates, const_boolean, const_date, const_date_array, const_number, const_number_array, const_string, const_string_array, createFullRegistry, createItemCore, createItemTypeDefinitionRegistry, createRichTextContent, createSeededRandom, ctx_custom_expression, ctx_custom_value, ctx_locale, ctx_pflag_date, ctx_pflag_is_defined, ctx_pflag_num, ctx_pflag_string, date_add_days, date_add_months, date_add_years, date_diff, date_eq, date_gt, date_gte, date_lt, date_lte, date_max, date_min, decodeExportColumnId, decodeJsonPointerSegment, deserializeSurveyItemPrefill, deserializeTemplateValue, deserializeTemplateValues, differenceCalendarDates, encodeExportColumnId, encodeJsonPointerSegment, eq, escapeCsvCell, exportSingleResponseSlot, expressionReferenceDiagnosticKey, findIntroducedBlockingExpressionReferenceDiagnostics, findIntroducedExpressionReferenceDiagnosticsIncludingAdvisories, flattenTree, formatCalendarDate, formatProjectedValue, generateCodebook, generateCodingKey, generateExportCodebook, generateId, getAssetUsagesFromContent, getCompositionLabel, getContentPlainText, getDefaultProjectionIssues, getItemExpressionDefinition, getPlainTextFromRichTextContent, getValueAtJsonPointer, gt, gte, hasRenderableRichTextBlock, hasRenderableRichTextContent, in_range, initValueForType, invalidResponseDomainValues, isBlockingSurveyExpressionReferenceDiagnostic, isBuiltInItemType, isCalendarDate, isContentEmpty, isLegacyItemGroupComponent, isLegacySurveyGroupItem, isResponseValue, isValidResponseVariableName, lt, lte, max, min, optionMapping, or, parseJsonPointer, prefillTargetsEqual, projectSlot, replaceResponseVariableReferences, resolveUniqueItemKey, responseValueElements, responseValueReferenceIds, responseValuesEqual, response_boolean, response_date, response_date_array, response_number, response_number_array, response_string, response_string_array, serializeSurveyItemPrefill, serializeTemplateValue, serializeTemplateValues, serializeToCsv, shuffleArray, shuffleIndices, str_eq, str_list_contains, structuredCloneMethod, suggestResponseVariableName, sum, toItemTypeDefinitionRegistry, validateLocale, validateResponseSlotValue };
2373
1323
 
2374
1324
  //# sourceMappingURL=index.mjs.map