@case-framework/survey-core 0.7.0 → 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,975 +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 ITEM_DISPLAY_DEPENDENCY_OWNER_SCOPE_POLICY = {
551
- component: false,
552
- item: true,
553
- survey: false
554
- };
555
- const ITEM_DISPLAY_DEPENDENCY_SURFACE_POLICY = {
556
- "display-condition": true,
557
- "disabled-condition": false,
558
- validation: false,
559
- "prefill-condition": false,
560
- "prefill-source": false,
561
- "template-value": false
562
- };
563
- /**
564
- * Display-condition cycles are advisory because responses may be populated before visibility is
565
- * evaluated, including through runtime prefills that are unavailable to static survey analysis.
566
- * Concrete expression integrity failures remain blocking. This exhaustive map is the source of
567
- * truth copied onto every analyzer-produced diagnostic.
568
- */
569
- const SURVEY_EXPRESSION_REFERENCE_DIAGNOSTIC_POLICY = {
570
- "invalid-expression": {
571
- category: "reference-integrity",
572
- disposition: "blocking"
573
- },
574
- "unknown-response-ref": {
575
- category: "reference-integrity",
576
- disposition: "blocking"
577
- },
578
- "expression-reference-type-mismatch": {
579
- category: "reference-integrity",
580
- disposition: "blocking"
581
- },
582
- "unknown-reference-value": {
583
- category: "reference-integrity",
584
- disposition: "blocking"
585
- },
586
- "display-condition-cycle": {
587
- category: "display-dependency",
588
- disposition: "advisory"
589
- }
590
- };
591
- const createExpressionReferenceDiagnostic = (diagnostic) => ({
592
- ...diagnostic,
593
- ...SURVEY_EXPRESSION_REFERENCE_DIAGNOSTIC_POLICY[diagnostic.code]
594
- });
595
- const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
596
- const isJsonExpression = (value) => isRecord$1(value) && typeof value.type === "string";
597
- const collectItemExpressionLocations = (item) => {
598
- const locations = [];
599
- const add = (expression, location, ownerScope, surface) => {
600
- if (!isJsonExpression(expression)) return;
601
- locations.push({
602
- expression,
603
- location,
604
- ownerItemId: item.id,
605
- ownerItemKey: item.key,
606
- ownerScope,
607
- surface
608
- });
609
- };
610
- add(item.displayConditions?.root, "displayConditions.root", "item", "display-condition");
611
- for (const [componentId, expression] of Object.entries(item.displayConditions?.components ?? {})) add(expression, `displayConditions.components.${componentId}`, "component", "display-condition");
612
- for (const [componentId, expression] of Object.entries(item.disabledConditions?.components ?? {})) add(expression, `disabledConditions.components.${componentId}`, "component", "disabled-condition");
613
- for (const [validationKey, expression] of Object.entries(item.validations ?? {})) add(expression, `validations.${validationKey}`, "item", "validation");
614
- for (const [prefillIndex, prefill] of (item.prefills ?? []).entries()) {
615
- const prefillId = prefill.id || String(prefillIndex);
616
- add(prefill.when, `prefills.${prefillId}.when`, "item", "prefill-condition");
617
- if (prefill.source.type === "expression") add(prefill.source.expression, `prefills.${prefillId}.source.expression`, "item", "prefill-source");
618
- }
619
- return locations;
620
- };
621
- const collectSurveyExpressionLocations = (survey) => [...survey.surveyItems.flatMap(collectItemExpressionLocations), ...Object.entries(survey.templateValues ?? {}).flatMap(([templateKey, templateValue]) => templateValue.expression ? [{
622
- expression: templateValue.expression,
623
- location: `templateValues.${templateKey}.expression`,
624
- ownerScope: "survey",
625
- surface: "template-value"
626
- }] : [])];
627
- const candidateRefs = (unknownRef, availableRefs) => {
628
- let parsed;
629
- try {
630
- parsed = new ValueReference(unknownRef);
631
- } catch {
632
- return availableRefs.slice(0, 5);
633
- }
634
- const sameItemAndMethod = availableRefs.filter((candidate) => {
635
- try {
636
- const candidateRef = new ValueReference(candidate);
637
- return candidateRef.itemId === parsed?.itemId && candidateRef.name === parsed?.name;
638
- } catch {
639
- 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]);
640
61
  }
641
- });
642
- return (sameItemAndMethod.length > 0 ? sameItemAndMethod : availableRefs).slice(0, 5);
643
- };
644
- const findCyclicItemDisplayDependencyEdges = ({ edges, itemIds }) => {
645
- const adjacency = new Map(itemIds.map((itemId) => [itemId, /* @__PURE__ */ new Set()]));
646
- const reverseAdjacency = new Map(itemIds.map((itemId) => [itemId, /* @__PURE__ */ new Set()]));
647
- for (const edge of edges) {
648
- adjacency.get(edge.fromItemId)?.add(edge.toItemId);
649
- reverseAdjacency.get(edge.toItemId)?.add(edge.fromItemId);
650
- }
651
- const finishOrder = [];
652
- const visited = /* @__PURE__ */ new Set();
653
- for (const startItemId of itemIds) {
654
- if (visited.has(startItemId)) continue;
655
- const stack = [{
656
- expanded: false,
657
- itemId: startItemId
658
- }];
659
- while (stack.length > 0) {
660
- const current = stack.pop();
661
- if (!current) break;
662
- if (current.expanded) {
663
- finishOrder.push(current.itemId);
664
- 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`);
665
74
  }
666
- if (visited.has(current.itemId)) continue;
667
- visited.add(current.itemId);
668
- stack.push({
669
- expanded: true,
670
- itemId: current.itemId
671
- });
672
- const dependencies = [...adjacency.get(current.itemId) ?? []];
673
- for (let index = dependencies.length - 1; index >= 0; index -= 1) {
674
- const dependencyId = dependencies[index];
675
- if (visited.has(dependencyId)) continue;
676
- stack.push({
677
- expanded: false,
678
- itemId: dependencyId
679
- });
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})`);
680
85
  }
681
86
  }
682
- }
683
- const componentByItemId = /* @__PURE__ */ new Map();
684
- const componentMembers = /* @__PURE__ */ new Map();
685
- for (const startItemId of finishOrder.reverse()) {
686
- if (componentByItemId.has(startItemId)) continue;
687
- const componentId = componentMembers.size;
688
- const members = [];
689
- const stack = [startItemId];
690
- componentByItemId.set(startItemId, componentId);
691
- while (stack.length > 0) {
692
- const itemId = stack.pop();
693
- if (!itemId) break;
694
- members.push(itemId);
695
- for (const dependencyOwnerId of reverseAdjacency.get(itemId) ?? []) {
696
- if (componentByItemId.has(dependencyOwnerId)) continue;
697
- componentByItemId.set(dependencyOwnerId, componentId);
698
- stack.push(dependencyOwnerId);
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);
699
114
  }
700
- }
701
- componentMembers.set(componentId, members.sort());
702
- }
703
- return edges.flatMap((edge) => {
704
- const componentId = componentByItemId.get(edge.fromItemId);
705
- if (componentId === void 0 || componentId !== componentByItemId.get(edge.toItemId)) return [];
706
- const cycleItemIds = componentMembers.get(componentId) ?? [];
707
- if (edge.fromItemId !== edge.toItemId && cycleItemIds.length < 2) return [];
708
- return [{
709
- edge,
710
- cycleItemIds
711
- }];
712
- });
713
- };
714
- const inferExpressionType = (expression, slots) => {
715
- if (!expression) return void 0;
716
- if (expression.type === ExpressionType.Const) return expression.value?.type;
717
- if (expression.type === ExpressionType.ResponseVariable) return slots.get(expression.variableRef)?.valueType;
718
- if (expression.type === ExpressionType.ContextVariable) return expression.contextType === "locale" ? ValueType.string : expression.asType;
719
- switch (expression.functionName) {
720
- case "and":
721
- case "or":
722
- case "not":
723
- case "eq":
724
- case "gt":
725
- case "gte":
726
- case "lt":
727
- case "lte":
728
- case "in_range":
729
- case "list_contains":
730
- case "str_eq":
731
- case "date_eq": return ValueType.boolean;
732
- case "sum":
733
- case "min":
734
- case "max": return ValueType.number;
735
- }
736
- };
737
- const itemTypeForList = (type) => {
738
- switch (type) {
739
- case ValueType.stringArray: return ValueType.string;
740
- case ValueType.referenceArray: return ValueType.reference;
741
- case ValueType.numberArray: return ValueType.number;
742
- case ValueType.dateArray: return ValueType.date;
743
- case ValueType.durationArray: return ValueType.duration;
744
- default: return;
745
- }
746
- };
747
- const inspectFunctionReferenceCompatibility = ({ diagnostics, expression, location, slots }) => {
748
- if (expression.type !== ExpressionType.Function) return;
749
- expression.arguments.forEach((argument) => {
750
- if (argument) inspectFunctionReferenceCompatibility({
751
- diagnostics,
752
- expression: argument,
753
- location,
754
- slots
755
- });
756
- });
757
- const [left, right] = expression.arguments;
758
- const leftType = inferExpressionType(left, slots);
759
- const rightType = inferExpressionType(right, slots);
760
- if (expression.functionName === "list_contains" ? Boolean(leftType && rightType && itemTypeForList(leftType) !== rightType) : expression.functionName === "eq" ? Boolean(leftType && rightType && leftType !== rightType) : false) diagnostics.push({
761
- code: "expression-reference-type-mismatch",
762
- message: `${location.location} compares incompatible values (${leftType} and ${rightType}).`,
763
- location: location.location,
764
- ownerItemId: location.ownerItemId,
765
- ownerItemKey: location.ownerItemKey
766
- });
767
- if (expression.functionName !== "eq" && expression.functionName !== "list_contains") return;
768
- const pairs = [[left, right], [right, left]];
769
- for (const [variable, constant] of pairs) {
770
- if (variable?.type !== ExpressionType.ResponseVariable || constant?.type !== ExpressionType.Const || constant.value?.type !== ValueType.reference) continue;
771
- const slot = slots.get(variable.variableRef);
772
- if (slot?.allowedReferenceValues && !slot.allowedReferenceValues.includes(constant.value.value)) diagnostics.push({
773
- code: "unknown-reference-value",
774
- message: `${location.location} compares "${variable.variableRef}" with missing option "${constant.value.value}".`,
775
- location: location.location,
776
- ownerItemId: location.ownerItemId,
777
- ownerItemKey: location.ownerItemKey,
778
- reference: variable.variableRef,
779
- referenceValue: constant.value.value,
780
- candidates: slot.allowedReferenceValues.slice(0, 5)
781
- });
782
- }
783
- };
784
- const analyzeSurveyExpressionReferences = ({ responseSlots, survey }) => {
785
- const slots = new Map(responseSlots.map((slot) => [slot.ref, slot]));
786
- const availableRefs = responseSlots.map((slot) => slot.ref);
787
- const diagnostics = [];
788
- const surveyItemIds = survey.surveyItems.map((item) => item.id);
789
- const surveyItemIdSet = new Set(surveyItemIds);
790
- const itemDisplayDependencyEdges = [];
791
- for (const location of collectSurveyExpressionLocations(survey)) {
792
- let parsed;
793
- try {
794
- parsed = Expression.deserialize(location.expression);
795
- } catch (error) {
796
- diagnostics.push({
797
- code: "invalid-expression",
798
- message: `${location.location} contains invalid expression JSON: ${error instanceof Error ? error.message : String(error)}`,
799
- location: location.location,
800
- ownerItemId: location.ownerItemId,
801
- ownerItemKey: location.ownerItemKey
802
- });
803
- continue;
804
- }
805
- for (const ref of parsed?.responseVariableRefs ?? []) {
806
- const reference = ref.toString();
807
- if (location.ownerItemId && ITEM_DISPLAY_DEPENDENCY_OWNER_SCOPE_POLICY[location.ownerScope] && ITEM_DISPLAY_DEPENDENCY_SURFACE_POLICY[location.surface] && surveyItemIdSet.has(ref.itemId)) itemDisplayDependencyEdges.push({
808
- fromItemId: location.ownerItemId,
809
- location,
810
- reference,
811
- toItemId: ref.itemId
812
- });
813
- if (!slots.has(reference)) diagnostics.push({
814
- code: "unknown-response-ref",
815
- message: `${location.location} references unknown response slot "${reference}".`,
816
- location: location.location,
817
- ownerItemId: location.ownerItemId,
818
- ownerItemKey: location.ownerItemKey,
819
- reference,
820
- candidates: candidateRefs(reference, availableRefs)
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
821
124
  });
822
- }
823
- inspectFunctionReferenceCompatibility({
824
- diagnostics,
825
- expression: location.expression,
826
- location,
827
- 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);
828
131
  });
829
- }
830
- for (const { cycleItemIds, edge } of findCyclicItemDisplayDependencyEdges({
831
- edges: itemDisplayDependencyEdges,
832
- itemIds: surveyItemIds
833
- })) {
834
- const isSelfReference = edge.fromItemId === edge.toItemId;
835
- diagnostics.push({
836
- code: "display-condition-cycle",
837
- message: isSelfReference ? `${edge.location.location} for item "${edge.fromItemId}" references its own response "${edge.reference}", creating a circular display dependency.` : `${edge.location.location} for item "${edge.fromItemId}" references item "${edge.toItemId}" within a circular display dependency among ${cycleItemIds.map((itemId) => `"${itemId}"`).join(", ")}.`,
838
- location: edge.location.location,
839
- ownerItemId: edge.location.ownerItemId,
840
- ownerItemKey: edge.location.ownerItemKey,
841
- reference: edge.reference
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));
842
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
+ };
843
160
  }
844
- return diagnostics.map(createExpressionReferenceDiagnostic);
845
- };
846
- const expressionReferenceDiagnosticKey = (diagnostic) => [
847
- diagnostic.code,
848
- diagnostic.ownerItemId ?? "survey",
849
- diagnostic.location,
850
- diagnostic.reference ?? "",
851
- diagnostic.referenceValue ?? ""
852
- ].join("\0");
853
- const findIntroducedExpressionReferenceDiagnosticsIncludingAdvisories = ({ after, before }) => {
854
- const existing = new Set(before.map(expressionReferenceDiagnosticKey));
855
- return after.filter((diagnostic) => !existing.has(expressionReferenceDiagnosticKey(diagnostic)));
856
- };
857
- const isBlockingSurveyExpressionReferenceDiagnostic = (diagnostic) => {
858
- switch (diagnostic.disposition) {
859
- case "blocking": return true;
860
- case "advisory": return false;
861
- default: return true;
862
- }
863
- };
864
- const findIntroducedBlockingExpressionReferenceDiagnostics = ({ after, before }) => findIntroducedExpressionReferenceDiagnosticsIncludingAdvisories({
865
- after,
866
- before
867
- }).filter(isBlockingSurveyExpressionReferenceDiagnostic);
868
- const replaceResponseVariableReferences = (value, replacements) => {
869
- let changed = false;
870
- const visit = (current) => {
871
- if (Array.isArray(current)) return current.map(visit);
872
- if (!isRecord$1(current)) return current;
873
- const next = Object.fromEntries(Object.entries(current).map(([key, entry]) => [key, visit(entry)]));
874
- if (next.type === ExpressionType.ResponseVariable && typeof next.variableRef === "string" && replacements[next.variableRef]) {
875
- next.variableRef = replacements[next.variableRef];
876
- changed = true;
877
- }
878
- return next;
879
- };
880
- const rewritten = visit(value);
881
- return {
882
- changed,
883
- value: rewritten
884
- };
885
- };
886
- const responseValueReferenceIds = (values) => values?.flatMap((value) => value.type === ValueType.reference ? [value.value] : value.type === ValueType.referenceArray ? value.value : []);
887
- //#endregion
888
- //#region src/expressions/editors/expression-editor.ts
889
- var ExpressionEditor = class {
890
- returnType;
891
- _editorConfig;
892
- get editorConfig() {
893
- return this._editorConfig;
894
- }
895
- withEditorConfig(editorConfig) {
896
- this._editorConfig = editorConfig;
897
- return this;
898
- }
899
- };
900
- var ConstStringArrayEditor = class extends ExpressionEditor {
901
- returnType = ValueType.stringArray;
902
- _values;
903
- constructor(values, editorConfig) {
904
- super();
905
- this._values = values;
906
- this._editorConfig = editorConfig;
907
- }
908
- get values() {
909
- return this._values;
910
- }
911
- set values(values) {
912
- this._values = values;
913
- }
914
- getExpression() {
915
- return new ConstExpression({
916
- type: ValueType.stringArray,
917
- value: this._values
918
- }, this._editorConfig);
919
- }
920
- };
921
- var ConstStringEditor = class extends ExpressionEditor {
922
- returnType = ValueType.string;
923
- _value;
924
- constructor(value, editorConfig) {
925
- super();
926
- this._value = value;
927
- this._editorConfig = editorConfig;
928
- }
929
- get value() {
930
- return this._value;
931
- }
932
- set value(value) {
933
- this._value = value;
934
- }
935
- getExpression() {
936
- return new ConstExpression({
937
- type: ValueType.string,
938
- value: this._value
939
- }, this._editorConfig);
940
- }
941
- };
942
- var ConstNumberEditor = class extends ExpressionEditor {
943
- returnType = ValueType.number;
944
- _value;
945
- constructor(value, editorConfig) {
946
- super();
947
- this._value = value;
948
- this._editorConfig = editorConfig;
949
- }
950
- get value() {
951
- return this._value;
952
- }
953
- set value(value) {
954
- this._value = value;
955
- }
956
- getExpression() {
957
- return new ConstExpression({
958
- type: ValueType.number,
959
- value: this._value
960
- }, this._editorConfig);
961
- }
962
- };
963
- var ConstBooleanEditor = class extends ExpressionEditor {
964
- returnType = ValueType.boolean;
965
- _value;
966
- constructor(value, editorConfig) {
967
- super();
968
- this._value = value;
969
- this._editorConfig = editorConfig;
970
- }
971
- get value() {
972
- return this._value;
973
- }
974
- set value(value) {
975
- this._value = value;
976
- }
977
- getExpression() {
978
- return new ConstExpression({
979
- type: ValueType.boolean,
980
- value: this._value
981
- }, this._editorConfig);
982
- }
983
- };
984
- var ConstDateEditor = class extends ExpressionEditor {
985
- returnType = ValueType.date;
986
- _value;
987
- constructor(value, editorConfig) {
988
- super();
989
- this._value = value;
990
- this._editorConfig = editorConfig;
991
- }
992
- get value() {
993
- return this._value;
994
- }
995
- set value(value) {
996
- this._value = value;
997
- }
998
- getExpression() {
999
- const value = Math.floor(this._value.getTime() / 1e3);
1000
- return new ConstExpression({
1001
- type: ValueType.date,
1002
- value
1003
- }, this._editorConfig);
1004
- }
1005
- };
1006
- var ConstNumberArrayEditor = class extends ExpressionEditor {
1007
- returnType = ValueType.numberArray;
1008
- _values;
1009
- constructor(values, editorConfig) {
1010
- super();
1011
- this._values = values;
1012
- this._editorConfig = editorConfig;
1013
- }
1014
- get values() {
1015
- return this._values;
1016
- }
1017
- set values(values) {
1018
- this._values = values;
1019
- }
1020
- getExpression() {
1021
- return new ConstExpression({
1022
- type: ValueType.numberArray,
1023
- value: this._values
1024
- }, this._editorConfig);
1025
- }
1026
- };
1027
- var ConstDateArrayEditor = class extends ExpressionEditor {
1028
- returnType = ValueType.dateArray;
1029
- _values;
1030
- constructor(values, editorConfig) {
1031
- super();
1032
- this._values = values;
1033
- this._editorConfig = editorConfig;
1034
- }
1035
- get values() {
1036
- return this._values;
1037
- }
1038
- set values(values) {
1039
- this._values = values;
1040
- }
1041
- getExpression() {
1042
- const value = this._values.map((date) => Math.floor(date.getTime() / 1e3));
1043
- return new ConstExpression({
1044
- type: ValueType.dateArray,
1045
- value
1046
- }, this._editorConfig);
1047
- }
1048
- };
1049
- var ResponseVariableEditor = class extends ExpressionEditor {
1050
- _variableName;
1051
- _variableRef;
1052
- constructor(variableName, variableType, editorConfig) {
1053
- super();
1054
- this._variableName = variableName;
1055
- this._variableRef = new ValueReference(variableName);
1056
- this._editorConfig = editorConfig;
1057
- this.returnType = variableType;
1058
- }
1059
- get variableName() {
1060
- return this._variableName;
1061
- }
1062
- get variableRef() {
1063
- return this._variableRef;
1064
- }
1065
- getExpression() {
1066
- return new ResponseVariableExpression(this._variableName, this._editorConfig);
1067
- }
1068
- };
1069
- var ContextVariableEditor = class extends ExpressionEditor {
1070
- _contextType;
1071
- _key;
1072
- _args;
1073
- _asType;
1074
- constructor(contextType, key, args, asType, editorConfig) {
1075
- super();
1076
- this._contextType = contextType;
1077
- this._key = key;
1078
- this._args = args;
1079
- this._asType = asType;
1080
- this._editorConfig = editorConfig;
1081
- }
1082
- getExpression() {
1083
- return new ContextVariableExpression(this._contextType, this._key?.getExpression(), this._args?.map((arg) => arg.getExpression()), this._asType, this._editorConfig);
1084
- }
1085
- };
1086
- var CtxLocaleEditor = class extends ContextVariableEditor {
1087
- returnType = ValueType.string;
1088
- constructor(editorConfig) {
1089
- super(ContextVariableType.Locale, void 0, void 0, ValueType.string, editorConfig);
1090
- }
1091
- };
1092
- var CtxPFlagIsDefinedEditor = class extends ContextVariableEditor {
1093
- returnType = ValueType.boolean;
1094
- constructor(key, editorConfig) {
1095
- super(ContextVariableType.ParticipantFlag, key, void 0, ValueType.boolean, editorConfig);
1096
- }
1097
- };
1098
- var CtxPFlagStringEditor = class extends ContextVariableEditor {
1099
- returnType = ValueType.string;
1100
- constructor(key, editorConfig) {
1101
- super(ContextVariableType.ParticipantFlag, key, void 0, ValueType.string, editorConfig);
1102
- }
1103
- };
1104
- var CtxPFlagNumEditor = class extends ContextVariableEditor {
1105
- returnType = ValueType.number;
1106
- constructor(key, editorConfig) {
1107
- super(ContextVariableType.ParticipantFlag, key, void 0, ValueType.number, editorConfig);
1108
- }
1109
- };
1110
- var CtxPFlagDateEditor = class extends ContextVariableEditor {
1111
- returnType = ValueType.date;
1112
- constructor(key, editorConfig) {
1113
- super(ContextVariableType.ParticipantFlag, key, void 0, ValueType.date, editorConfig);
1114
- }
1115
- };
1116
- var CtxCustomValueEditor = class extends ContextVariableEditor {
1117
- constructor(key, asType, editorConfig) {
1118
- super(ContextVariableType.CustomValue, key, void 0, asType, editorConfig);
1119
- this.returnType = asType;
1120
- }
1121
- };
1122
- var CtxCustomExpressionEditor = class extends ContextVariableEditor {
1123
- constructor(key, args, asType, editorConfig) {
1124
- super(ContextVariableType.CustomExpression, key, args, asType, editorConfig);
1125
- this.returnType = asType;
1126
- }
1127
- };
1128
- var GroupExpressionEditor = class extends ExpressionEditor {
1129
- _args;
1130
- constructor(args, editorConfig) {
1131
- super();
1132
- this._args = args;
1133
- this._editorConfig = editorConfig;
1134
- }
1135
- get args() {
1136
- return this._args;
1137
- }
1138
- addArg(arg, position) {
1139
- if (position === void 0) this._args.push(arg);
1140
- else this._args.splice(position, 0, arg);
1141
- }
1142
- removeArg(position) {
1143
- this._args.splice(position, 1);
1144
- }
1145
- replaceArg(position, arg) {
1146
- this._args[position] = arg;
1147
- }
1148
- swapArgs(activeIndex, overIndex) {
1149
- const newOrder = [...this._args];
1150
- newOrder.splice(activeIndex, 1);
1151
- newOrder.splice(overIndex, 0, this._args[activeIndex]);
1152
- this._args = newOrder;
1153
- }
1154
- };
1155
- var AndExpressionEditor = class extends GroupExpressionEditor {
1156
- returnType = ValueType.boolean;
1157
- constructor(args, editorConfig) {
1158
- if (args.some((arg) => arg.returnType !== ValueType.boolean)) throw new Error("And expression editor must have boolean arguments");
1159
- super(args, editorConfig);
1160
- }
1161
- getExpression() {
1162
- return new FunctionExpression("and", this.args.map((arg) => arg.getExpression()), this._editorConfig);
1163
- }
1164
- };
1165
- var OrExpressionEditor = class extends GroupExpressionEditor {
1166
- returnType = ValueType.boolean;
1167
- constructor(args, editorConfig) {
1168
- if (args.some((arg) => arg.returnType !== ValueType.boolean)) throw new Error("Or expression editor must have boolean arguments");
1169
- super(args, editorConfig);
1170
- }
1171
- getExpression() {
1172
- return new FunctionExpression("or", this.args.map((arg) => arg.getExpression()), this._editorConfig);
1173
- }
1174
- };
1175
- var StrListContainsExpressionEditor = class extends ExpressionEditor {
1176
- returnType = ValueType.boolean;
1177
- _list;
1178
- _item;
1179
- constructor(list, item, editorConfig) {
1180
- super();
1181
- if (list.returnType !== ValueType.stringArray) throw new Error("List contains expression editor must have a string array list");
1182
- if (item.returnType !== ValueType.string) throw new Error("List contains expression editor must have a string item");
1183
- this._list = list;
1184
- this._item = item;
1185
- this._editorConfig = editorConfig;
1186
- }
1187
- get list() {
1188
- return this._list;
1189
- }
1190
- get item() {
1191
- return this._item;
1192
- }
1193
- set list(list) {
1194
- this._list = list;
1195
- }
1196
- set item(item) {
1197
- this._item = item;
1198
- }
1199
- getExpression() {
1200
- return new FunctionExpression("list_contains", [this._list?.getExpression(), this._item?.getExpression()], this._editorConfig);
1201
- }
1202
- };
1203
- var StrEqExpressionEditor = class extends ExpressionEditor {
1204
- returnType = ValueType.boolean;
1205
- _a;
1206
- _b;
1207
- constructor(a, b, editorConfig) {
1208
- super();
1209
- this._a = a;
1210
- this._b = b;
1211
- this._editorConfig = editorConfig;
1212
- }
1213
- get a() {
1214
- return this._a;
1215
- }
1216
- get b() {
1217
- return this._b;
1218
- }
1219
- set a(a) {
1220
- this._a = a;
1221
- }
1222
- set b(b) {
1223
- this._b = b;
1224
- }
1225
- getExpression() {
1226
- return new FunctionExpression("str_eq", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
1227
- }
1228
- };
1229
- var EqExpressionEditor = class extends ExpressionEditor {
1230
- returnType = ValueType.boolean;
1231
- _a;
1232
- _b;
1233
- constructor(a, b, editorConfig) {
1234
- super();
1235
- this._a = a;
1236
- this._b = b;
1237
- this._editorConfig = editorConfig;
1238
- }
1239
- get a() {
1240
- return this._a;
1241
- }
1242
- get b() {
1243
- return this._b;
1244
- }
1245
- set a(a) {
1246
- this._a = a;
1247
- }
1248
- set b(b) {
1249
- this._b = b;
1250
- }
1251
- getExpression() {
1252
- return new FunctionExpression("eq", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
1253
- }
1254
- };
1255
- var GtExpressionEditor = class extends ExpressionEditor {
1256
- returnType = ValueType.boolean;
1257
- _a;
1258
- _b;
1259
- constructor(a, b, editorConfig) {
1260
- super();
1261
- this._a = a;
1262
- this._b = b;
1263
- this._editorConfig = editorConfig;
1264
- }
1265
- get a() {
1266
- return this._a;
1267
- }
1268
- get b() {
1269
- return this._b;
1270
- }
1271
- set a(a) {
1272
- this._a = a;
1273
- }
1274
- set b(b) {
1275
- this._b = b;
1276
- }
1277
- getExpression() {
1278
- return new FunctionExpression("gt", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
1279
- }
1280
- };
1281
- var GteExpressionEditor = class extends ExpressionEditor {
1282
- returnType = ValueType.boolean;
1283
- _a;
1284
- _b;
1285
- constructor(a, b, editorConfig) {
1286
- super();
1287
- this._a = a;
1288
- this._b = b;
1289
- this._editorConfig = editorConfig;
1290
- }
1291
- get a() {
1292
- return this._a;
1293
- }
1294
- get b() {
1295
- return this._b;
1296
- }
1297
- set a(a) {
1298
- this._a = a;
1299
- }
1300
- set b(b) {
1301
- this._b = b;
1302
- }
1303
- getExpression() {
1304
- return new FunctionExpression("gte", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
1305
- }
1306
- };
1307
- var LtExpressionEditor = class extends ExpressionEditor {
1308
- returnType = ValueType.boolean;
1309
- _a;
1310
- _b;
1311
- constructor(a, b, editorConfig) {
1312
- super();
1313
- this._a = a;
1314
- this._b = b;
1315
- this._editorConfig = editorConfig;
1316
- }
1317
- get a() {
1318
- return this._a;
1319
- }
1320
- get b() {
1321
- return this._b;
1322
- }
1323
- set a(a) {
1324
- this._a = a;
1325
- }
1326
- set b(b) {
1327
- this._b = b;
1328
- }
1329
- getExpression() {
1330
- return new FunctionExpression("lt", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
1331
- }
1332
- };
1333
- var LteExpressionEditor = class extends ExpressionEditor {
1334
- returnType = ValueType.boolean;
1335
- _a;
1336
- _b;
1337
- constructor(a, b, editorConfig) {
1338
- super();
1339
- this._a = a;
1340
- this._b = b;
1341
- this._editorConfig = editorConfig;
1342
- }
1343
- get a() {
1344
- return this._a;
1345
- }
1346
- get b() {
1347
- return this._b;
1348
- }
1349
- set b(b) {
1350
- this._b = b;
1351
- }
1352
- set a(a) {
1353
- this._a = a;
1354
- }
1355
- getExpression() {
1356
- return new FunctionExpression("lte", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
1357
- }
1358
- };
1359
- var InRangeExpressionEditor = class extends ExpressionEditor {
1360
- returnType = ValueType.boolean;
1361
- value;
1362
- min;
1363
- max;
1364
- inclusive;
1365
- constructor(value, min, max, inclusive, editorConfig) {
1366
- super();
1367
- this.value = value;
1368
- this.min = min;
1369
- this.max = max;
1370
- this.inclusive = inclusive;
1371
- this._editorConfig = editorConfig;
1372
- }
1373
- getExpression() {
1374
- return new FunctionExpression("in_range", [
1375
- this.value?.getExpression(),
1376
- this.min?.getExpression(),
1377
- this.max?.getExpression(),
1378
- this.inclusive?.getExpression()
1379
- ], this._editorConfig);
1380
- }
1381
- };
1382
- var SumExpressionEditor = class extends GroupExpressionEditor {
1383
- returnType = ValueType.number;
1384
- constructor(args, editorConfig) {
1385
- super(args, editorConfig);
1386
- }
1387
- getExpression() {
1388
- return new FunctionExpression("sum", this.args.map((arg) => arg.getExpression()), this._editorConfig);
1389
- }
1390
- };
1391
- var MinExpressionEditor = class extends GroupExpressionEditor {
1392
- returnType = ValueType.number;
1393
- constructor(args, editorConfig) {
1394
- super(args, editorConfig);
1395
- }
1396
- getExpression() {
1397
- return new FunctionExpression("min", this.args.map((arg) => arg.getExpression()), this._editorConfig);
1398
- }
1399
- };
1400
- var MaxExpressionEditor = class extends GroupExpressionEditor {
1401
- returnType = ValueType.number;
1402
- constructor(args, editorConfig) {
1403
- super(args, editorConfig);
1404
- }
1405
- getExpression() {
1406
- return new FunctionExpression("max", this.args.map((arg) => arg.getExpression()), this._editorConfig);
1407
- }
1408
- };
1409
- //#endregion
1410
- //#region src/expressions/editors/expression-editor-generators.ts
1411
- const const_string_array = (...values) => {
1412
- return new ConstStringArrayEditor(values);
1413
- };
1414
- const const_string = (value) => {
1415
- return new ConstStringEditor(value);
1416
- };
1417
- const const_number_array = (...values) => {
1418
- return new ConstNumberArrayEditor(values);
1419
- };
1420
- const const_number = (value) => {
1421
- return new ConstNumberEditor(value);
1422
- };
1423
- const const_boolean = (value) => {
1424
- return new ConstBooleanEditor(value);
1425
- };
1426
- const const_date = (value) => {
1427
- return new ConstDateEditor(value);
1428
- };
1429
- const const_date_array = (...values) => {
1430
- return new ConstDateArrayEditor(values);
1431
- };
1432
- const response_string = (valueRef) => {
1433
- return new ResponseVariableEditor(valueRef, ValueType.string);
1434
- };
1435
- const response_string_array = (valueRef) => {
1436
- return new ResponseVariableEditor(valueRef, ValueType.stringArray);
1437
- };
1438
- const response_number = (valueRef) => {
1439
- return new ResponseVariableEditor(valueRef, ValueType.number);
1440
- };
1441
- const response_boolean = (valueRef) => {
1442
- return new ResponseVariableEditor(valueRef, ValueType.boolean);
1443
- };
1444
- const response_date = (valueRef) => {
1445
- return new ResponseVariableEditor(valueRef, ValueType.date);
1446
- };
1447
- const response_number_array = (valueRef) => {
1448
- return new ResponseVariableEditor(valueRef, ValueType.numberArray);
1449
- };
1450
- const response_date_array = (valueRef) => {
1451
- return new ResponseVariableEditor(valueRef, ValueType.dateArray);
1452
- };
1453
- const ctx_locale = () => {
1454
- return new CtxLocaleEditor();
1455
- };
1456
- const ctx_pflag_is_defined = (key) => {
1457
- return new CtxPFlagIsDefinedEditor(key);
1458
- };
1459
- const ctx_pflag_string = (key) => {
1460
- return new CtxPFlagStringEditor(key);
1461
- };
1462
- const ctx_pflag_num = (key) => {
1463
- return new CtxPFlagNumEditor(key);
1464
- };
1465
- const ctx_pflag_date = (key) => {
1466
- return new CtxPFlagDateEditor(key);
1467
- };
1468
- const ctx_custom_value = (key, dType) => {
1469
- return new CtxCustomValueEditor(key, dType);
1470
- };
1471
- const ctx_custom_expression = (key, args, returnType) => {
1472
- return new CtxCustomExpressionEditor(key, args, returnType);
1473
- };
1474
- const and = (...args) => {
1475
- return new AndExpressionEditor(args);
1476
- };
1477
- const or = (...args) => {
1478
- return new OrExpressionEditor(args);
1479
- };
1480
- const str_list_contains = (list, item) => {
1481
- return new StrListContainsExpressionEditor(list, item);
1482
- };
1483
- const str_eq = (a, b) => {
1484
- return new StrEqExpressionEditor(a, b);
1485
- };
1486
- const eq = (a, b) => {
1487
- return new EqExpressionEditor(a, b);
1488
- };
1489
- const gt = (a, b) => {
1490
- return new GtExpressionEditor(a, b);
1491
- };
1492
- const gte = (a, b) => {
1493
- return new GteExpressionEditor(a, b);
1494
- };
1495
- const lt = (a, b) => {
1496
- return new LtExpressionEditor(a, b);
1497
- };
1498
- const lte = (a, b) => {
1499
- return new LteExpressionEditor(a, b);
1500
- };
1501
- const in_range = (value, min, max, inclusive) => {
1502
- return new InRangeExpressionEditor(value, min, max, inclusive);
1503
- };
1504
- const sum = (...args) => {
1505
- return new SumExpressionEditor(args);
1506
- };
1507
- const min = (...args) => {
1508
- return new MinExpressionEditor(args);
1509
- };
1510
- const max = (...args) => {
1511
- return new MaxExpressionEditor(args);
1512
161
  };
1513
162
  //#endregion
1514
163
  //#region src/engine/engine.ts
1515
- var SurveyEngineCore = class {
164
+ var SurveyEngineCore = class SurveyEngineCore {
165
+ activeResponsePlan;
166
+ activeState;
167
+ responseDiagnostics = /* @__PURE__ */ new Map();
1516
168
  surveyDef;
1517
169
  renderedSurveyTree;
170
+ renderedItemsCache;
1518
171
  context;
1519
172
  locale;
1520
173
  sessionSeedValue;
@@ -1529,12 +182,24 @@ var SurveyEngineCore = class {
1529
182
  this._openedAt = this._getTimestamp();
1530
183
  this.sessionSeedValue = generateId();
1531
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;
1532
188
  this.context = context ? context : { locale: "en" };
1533
189
  this.locale = this.context.locale;
1534
- this.prefills = prefills ? prefills.reduce((acc, p) => {
1535
- acc[p.itemId] = SurveyItemResponse.deserialize(p);
1536
- return acc;
1537
- }, {}) : 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
+ }
1538
203
  this.prefillEnvironment = options?.prefillEnvironment;
1539
204
  this.dateLocales = dateLocales || [{
1540
205
  code: "en",
@@ -1542,7 +207,7 @@ var SurveyEngineCore = class {
1542
207
  }];
1543
208
  this.responses = this.initResponseObject(this.surveyDef.surveyItems);
1544
209
  this.initCache();
1545
- this.applyConfiguredPrefills();
210
+ if (options?.applyConfiguredPrefills !== false) this.applyConfiguredPrefills();
1546
211
  this.evalExpressions();
1547
212
  if (!survey.rootItem) throw new Error("constructor: root item not found");
1548
213
  this.renderedSurveyTree = this.renderGroup(survey.rootItem);
@@ -1553,7 +218,7 @@ var SurveyEngineCore = class {
1553
218
  }];
1554
219
  }
1555
220
  setContext(context) {
1556
- this.context = context;
221
+ this.updateContext(context);
1557
222
  }
1558
223
  getDateLocales() {
1559
224
  return this.dateLocales.slice();
@@ -1568,9 +233,10 @@ var SurveyEngineCore = class {
1568
233
  return found?.locale;
1569
234
  }
1570
235
  updateContext(context) {
236
+ const localeChanged = this.locale !== context.locale;
1571
237
  this.context = context;
1572
238
  this.locale = context.locale;
1573
- this._registerSurveyEvent({
239
+ if (localeChanged) this._registerSurveyEvent({
1574
240
  type: SurveyEventTypes.languageChanged,
1575
241
  timestamp: this._getTimestamp(),
1576
242
  value: context.locale
@@ -1578,21 +244,49 @@ var SurveyEngineCore = class {
1578
244
  this.evalExpressions();
1579
245
  this.reRenderSurveyTree();
1580
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
+ }
1581
256
  setResponse(targetKey, response) {
1582
257
  const target = this.getResponseItem(targetKey);
1583
258
  if (!target) throw new Error("setResponse: target not found for key: " + targetKey);
1584
- target.response = response;
1585
- this._registerSurveyEvent({
1586
- type: SurveyEventTypes.responeChanged,
1587
- timestamp: this._getTimestamp(),
1588
- itemId: targetKey
1589
- });
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
+ }
1590
276
  this.evalExpressions();
1591
277
  this.reRenderSurveyTree();
1592
278
  }
1593
279
  get openedAt() {
1594
280
  return this._openedAt;
1595
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
+ }
1596
290
  get sessionSeed() {
1597
291
  return this.sessionSeedValue;
1598
292
  }
@@ -1619,9 +313,7 @@ var SurveyEngineCore = class {
1619
313
  case "large":
1620
314
  max = this.surveyDef.maxItemsPerPage.large;
1621
315
  break;
1622
- case "small":
1623
- max = this.surveyDef.maxItemsPerPage.small;
1624
- break;
316
+ case "small": max = this.surveyDef.maxItemsPerPage.small;
1625
317
  }
1626
318
  if (currentPage.length >= max) {
1627
319
  pages.push([...currentPage]);
@@ -1631,8 +323,17 @@ var SurveyEngineCore = class {
1631
323
  if (currentPage.length > 0) pages.push([...currentPage]);
1632
324
  return pages;
1633
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
+ }
1634
334
  getRenderedSurveyItem(itemId) {
1635
- 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;
1636
337
  }
1637
338
  onPageChanged(pageIndex) {
1638
339
  this._registerSurveyEvent({
@@ -1644,11 +345,44 @@ var SurveyEngineCore = class {
1644
345
  getEvents() {
1645
346
  return this._events.slice();
1646
347
  }
348
+ /** Current session answers, including retained answers to hidden items. */
1647
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) {
1648
381
  const renderedPositions = /* @__PURE__ */ new Map();
1649
382
  flattenTree(this.renderedSurveyTree).filter((item) => item.type !== "page-break").forEach((item, index) => {
1650
383
  if (this.responses[item.id]) renderedPositions.set(item.id, index);
1651
384
  });
385
+ const active = includeHidden ? void 0 : this.activeEvaluation();
1652
386
  const responses = [];
1653
387
  this.surveyDef.surveyItems.forEach((item, itemId) => {
1654
388
  if (!item.isInteractive()) return;
@@ -1656,10 +390,31 @@ var SurveyEngineCore = class {
1656
390
  if (!response) return;
1657
391
  const position = renderedPositions.get(itemId);
1658
392
  response.meta = position === void 0 ? void 0 : { position };
1659
- 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);
1660
401
  });
1661
402
  return responses;
1662
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
+ }
1663
418
  getDisplayConditionValue(itemKey, componentKey) {
1664
419
  if (componentKey) return this.cache.displayConditions[itemKey]?.components?.[componentKey]?.result;
1665
420
  return this.cache.displayConditions[itemKey]?.root?.result;
@@ -1671,12 +426,9 @@ var SurveyEngineCore = class {
1671
426
  return this.cache.templateValues[templateValueKey]?.value;
1672
427
  }
1673
428
  getValidationValues(itemKey) {
1674
- const validations = this.cache.validations[itemKey];
1675
- if (!validations) return;
1676
- return Object.keys(validations).reduce((acc, validationKey) => {
1677
- acc[validationKey] = validations[validationKey].result;
1678
- return acc;
1679
- }, {});
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;
1680
432
  }
1681
433
  initCache() {
1682
434
  this.cache = {
@@ -1746,40 +498,39 @@ var SurveyEngineCore = class {
1746
498
  initResponseObject(items) {
1747
499
  const respGroup = {};
1748
500
  items.forEach((item, itemId) => {
1749
- if (!item.isInteractive) return;
501
+ if (!item.isInteractive()) return;
1750
502
  else {
1751
503
  const prefill = this.prefills?.[itemId];
1752
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 ?? []);
1753
507
  respGroup[itemId] = new SurveyItemResponse({
1754
508
  itemId,
1755
509
  itemType: item.type
1756
- }, applyPrefill ? prefill.response : void 0);
510
+ }, normalized.response);
1757
511
  }
1758
512
  });
1759
513
  return respGroup;
1760
514
  }
1761
515
  applyConfiguredPrefills() {
1762
- const evalEngine = new ExpressionEvaluator({
1763
- responses: this.responses,
1764
- surveyContext: this.context
1765
- });
1766
516
  const maxPasses = Math.max(this.surveyDef.surveyItems.size, 1);
1767
517
  for (let pass = 0; pass < maxPasses; pass++) {
1768
- this.evalTemplateValues(evalEngine);
518
+ this.evalTemplateValues(this.createEvaluator());
1769
519
  let changedInPass = false;
1770
520
  this.surveyDef.surveyItems.forEach((item) => {
1771
521
  if (!item.isInteractive()) return;
1772
- changedInPass = this.applyConfiguredPrefillsForItem(item, evalEngine) || changedInPass;
522
+ changedInPass = this.applyConfiguredPrefillsForItem(item) || changedInPass;
1773
523
  });
1774
524
  if (!changedInPass) break;
1775
525
  }
1776
- this.evalTemplateValues(evalEngine);
526
+ this.evalTemplateValues(this.createEvaluator());
1777
527
  }
1778
- applyConfiguredPrefillsForItem(item, evalEngine) {
528
+ applyConfiguredPrefillsForItem(item) {
1779
529
  const responseItem = this.responses[item.id];
1780
530
  if (!responseItem) return false;
1781
531
  let changed = false;
1782
532
  for (const prefill of item.prefills ?? []) {
533
+ const evalEngine = this.createEvaluator();
1783
534
  const targetSlot = item.resolvePrefillTarget(prefill.target);
1784
535
  if (!targetSlot) continue;
1785
536
  if ((prefill.apply ?? SurveyItemPrefillApplyMode.ifEmpty) === SurveyItemPrefillApplyMode.ifEmpty && responseItem.response?.get(targetSlot.slotId) !== void 0) continue;
@@ -1790,8 +541,11 @@ var SurveyEngineCore = class {
1790
541
  if (!normalizedValue) continue;
1791
542
  const nextResponse = responseItem.response?.clone() ?? new ResponseItem();
1792
543
  nextResponse.setSlotValue(targetSlot.slotId, normalizedValue);
1793
- responseItem.response = nextResponse.isEmpty() ? void 0 : nextResponse;
1794
- 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;
1795
549
  }
1796
550
  return changed;
1797
551
  }
@@ -1807,11 +561,9 @@ var SurveyEngineCore = class {
1807
561
  case "templateValue": return this.getTemplateValue(prefill.source.key);
1808
562
  case "previousResponse": {
1809
563
  if (!this.prefillEnvironment?.getPreviousResponse) return;
1810
- const refTarget = prefill.source.ref.target ?? prefill.target;
1811
564
  const resolved = this.prefillEnvironment.getPreviousResponse({
1812
565
  surveyKey: prefill.source.ref.surveyKey,
1813
- itemId: prefill.source.ref.itemId,
1814
- target: refTarget
566
+ slotId: prefill.source.ref.slotId
1815
567
  });
1816
568
  if (!resolved) return;
1817
569
  return resolved;
@@ -1877,6 +629,11 @@ var SurveyEngineCore = class {
1877
629
  };
1878
630
  }
1879
631
  renderGroup(groupDef, parent) {
632
+ if (!this.activeEvaluation().isItemVisible(groupDef.id)) return {
633
+ id: groupDef.id,
634
+ type: groupDef.type,
635
+ items: []
636
+ };
1880
637
  if (!parent) parent = {
1881
638
  id: groupDef.id,
1882
639
  type: "group",
@@ -1898,13 +655,48 @@ var SurveyEngineCore = class {
1898
655
  getResponseItem(itemId) {
1899
656
  return this.responses[itemId];
1900
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
+ }
1901
695
  evalExpressions() {
1902
- const evalEngine = new ExpressionEvaluator({
1903
- responses: this.responses,
1904
- surveyContext: this.context
1905
- });
696
+ this.activeState = this.activeResponsePlan.evaluate(this.responses, this.context);
697
+ const evalEngine = this.activeState.evaluator;
1906
698
  this.evalTemplateValues(evalEngine);
1907
- this.evalDisplayConditions(evalEngine);
699
+ this.evalDisplayConditions();
1908
700
  this.evalDisableConditions(evalEngine);
1909
701
  this.evalValidations(evalEngine);
1910
702
  }
@@ -1929,34 +721,18 @@ var SurveyEngineCore = class {
1929
721
  }
1930
722
  evaluatedValue = {
1931
723
  type: ValueType.string,
1932
- value: format(/* @__PURE__ */ new Date(evaluatedValue.value * 1e3), templateValue.templateDef.dateFormat)
724
+ value: formatCalendarDate(evaluatedValue.value, templateValue.templateDef.dateFormat)
1933
725
  };
1934
- break;
1935
- default: break;
1936
726
  }
1937
727
  this.cache.templateValues[templateValueKey].value = evaluatedValue;
1938
728
  });
1939
729
  }
1940
- evalDisplayConditions(evalEngine) {
1941
- Object.keys(this.cache.displayConditions).forEach((itemKey) => {
1942
- const displayCondition = this.cache.displayConditions[itemKey];
1943
- if (displayCondition.root) {
1944
- const resolvedValue = evalEngine.eval(displayCondition.root.expression);
1945
- if (resolvedValue === void 0 || resolvedValue.type !== ValueType.boolean) {
1946
- console.warn("evalDisplayConditions: display condition expression returned undefined: " + itemKey);
1947
- return;
1948
- }
1949
- this.cache.displayConditions[itemKey].root.result = resolvedValue.value;
1950
- }
1951
- if (displayCondition.components) Object.keys(displayCondition.components).forEach((componentKey) => {
1952
- const resolvedValue = evalEngine.eval(displayCondition.components[componentKey].expression);
1953
- if (resolvedValue === void 0 || resolvedValue.type !== ValueType.boolean) {
1954
- console.warn("evalDisplayConditions: display condition component expression returned undefined: " + itemKey + "." + componentKey);
1955
- return;
1956
- }
1957
- this.cache.displayConditions[itemKey].components[componentKey].result = resolvedValue.value;
1958
- });
1959
- });
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
+ }
1960
736
  }
1961
737
  evalDisableConditions(evalEngine) {
1962
738
  Object.keys(this.cache.disabledConditions).forEach((itemKey) => {
@@ -1978,6 +754,7 @@ var SurveyEngineCore = class {
1978
754
  const resolvedValue = evalEngine.eval(validation[validationKey].expression);
1979
755
  if (resolvedValue === void 0 || resolvedValue.type !== ValueType.boolean) {
1980
756
  console.warn("evalValidations: validation expression returned undefined: " + itemId + "." + validationKey);
757
+ this.cache.validations[itemId][validationKey].result = false;
1981
758
  return;
1982
759
  }
1983
760
  this.cache.validations[itemId][validationKey].result = resolvedValue.value;
@@ -1996,6 +773,9 @@ const flattenTree = (itemTree) => {
1996
773
  //#endregion
1997
774
  //#region src/json-patch.ts
1998
775
  var JsonPatchError = class extends Error {
776
+ code;
777
+ path;
778
+ operationIndex;
1999
779
  constructor(message, code, path, operationIndex) {
2000
780
  super(message);
2001
781
  this.code = code;
@@ -2053,7 +833,8 @@ const getValueAtSegments = (document, segments, path) => {
2053
833
  return current;
2054
834
  };
2055
835
  const getValueAtJsonPointer = (document, pointer) => {
2056
- return getValueAtSegments(document, parseJsonPointer(pointer), pointer);
836
+ const segments = parseJsonPointer(pointer);
837
+ return getValueAtSegments(document, segments, pointer);
2057
838
  };
2058
839
  const deepEqualJson = (left, right) => {
2059
840
  if (left === right) return true;
@@ -2161,9 +942,11 @@ const applyJsonPatch = (document, operations) => {
2161
942
  patchedDocument = applyAdd(patchedDocument, operation.path, value);
2162
943
  break;
2163
944
  }
2164
- case "test":
2165
- 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);
2166
948
  break;
949
+ }
2167
950
  }
2168
951
  } catch (error) {
2169
952
  if (error instanceof JsonPatchError) throw new JsonPatchError(error.message, error.code, error.path, operationIndex);
@@ -2173,93 +956,13 @@ const applyJsonPatch = (document, operations) => {
2173
956
  return patchedDocument;
2174
957
  };
2175
958
  //#endregion
2176
- //#region src/response-exporter/types.ts
2177
- /** Meta column keys in fixed order (common attributes first). */
2178
- const META_COLUMN_ORDER = [
2179
- "submittedAt",
2180
- "participantId",
2181
- "responseId",
2182
- "versionId"
2183
- ];
2184
- /** Built-in transform modes for slot export. */
2185
- const SlotTransformMode = {
2186
- default: "default",
2187
- mask: "mask",
2188
- dateFormat: "dateFormat"
2189
- };
2190
- /** Separator between item key path and slot coding path in default export headers. */
2191
- const EXPORT_COLUMN_SLOT_SEPARATOR = "__";
2192
- /** Source of an export column. */
2193
- const ExportColumnSource = {
2194
- meta: "meta",
2195
- context: "context",
2196
- response: "response"
2197
- };
2198
- //#endregion
2199
- //#region src/response-exporter/slot-formatter.ts
2200
- /** Default transform config when no profile override. */
2201
- const DEFAULT_TRANSFORM = { mode: SlotTransformMode.default };
2202
- /**
2203
- * Export a single response slot to a string. Pure function for unit testing.
2204
- * Uses default formatting by ValueType unless overridden by transformConfig.
2205
- */
2206
- function exportSingleResponseSlot(params) {
2207
- const { value, valueType, transformConfig } = params;
2208
- if (value === void 0 || value === null) return "";
2209
- const mode = transformConfig.mode;
2210
- if (mode === SlotTransformMode.mask) return maskValue(value, transformConfig.maskChar ?? "***");
2211
- if (mode === SlotTransformMode.dateFormat && (valueType === ValueType.date || valueType === ValueType.dateArray)) return formatDateValue(value, transformConfig.dateFormat?.pattern ?? "yyyy-MM-dd");
2212
- return formatDefault(value);
2213
- }
2214
- const BooleanExportValues = {
2215
- TRUE: "TRUE",
2216
- FALSE: "FALSE"
2217
- };
2218
- function formatDefault(value) {
2219
- switch (value.type) {
2220
- case ValueType.string: return String(value.value);
2221
- case ValueType.reference: return String(value.value);
2222
- case ValueType.boolean: return value.value ? BooleanExportValues.TRUE : BooleanExportValues.FALSE;
2223
- case ValueType.number: return value.precision === NumberPrecision.int ? String(Math.floor(value.value)) : String(value.value);
2224
- case ValueType.date: return (/* @__PURE__ */ new Date(value.value * 1e3)).toISOString();
2225
- case ValueType.duration: {
2226
- const d = value;
2227
- return `${d.precision === NumberPrecision.int ? Math.floor(d.value) : d.value} ${d.unit}`;
2228
- }
2229
- case ValueType.stringArray: return value.value.join("; ");
2230
- case ValueType.referenceArray: return value.value.join("; ");
2231
- case ValueType.numberArray: {
2232
- const arr = value.value;
2233
- const prec = value.precision;
2234
- const fmt = (n) => prec === NumberPrecision.int ? Math.floor(n) : n;
2235
- return arr.map(fmt).join("; ");
2236
- }
2237
- case ValueType.dateArray: return value.value.map((ts) => (/* @__PURE__ */ new Date(ts * 1e3)).toISOString()).join("; ");
2238
- case ValueType.durationArray: {
2239
- const d = value;
2240
- const prec = d.precision === NumberPrecision.int;
2241
- const fmt = (n) => prec ? Math.floor(n) : n;
2242
- return d.value.map((v) => `${fmt(v)} ${d.unit}`).join("; ");
2243
- }
2244
- default: return String(value.value ?? "");
2245
- }
2246
- }
2247
- function formatDateValue(value, pattern) {
2248
- if (value.type === ValueType.date) return format(/* @__PURE__ */ new Date(value.value * 1e3), pattern);
2249
- if (value.type === ValueType.dateArray) return value.value.map((ts) => format(/* @__PURE__ */ new Date(ts * 1e3), pattern)).join("; ");
2250
- return formatDefault(value);
2251
- }
2252
- function maskValue(value, maskChar) {
2253
- return valueToString(value) ? maskChar : "";
2254
- }
2255
- function valueToString(value) {
2256
- switch (value.type) {
2257
- case ValueType.string:
2258
- case ValueType.reference: return String(value.value);
2259
- case ValueType.stringArray:
2260
- case ValueType.referenceArray: return value.value.join(" ");
2261
- default: return String(value.value ?? "");
2262
- }
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"));
2263
966
  }
2264
967
  //#endregion
2265
968
  //#region src/response-exporter/csv-serializer.ts
@@ -2297,181 +1000,226 @@ function serializeToCsv(columns, rows, options) {
2297
1000
  }
2298
1001
  //#endregion
2299
1002
  //#region src/response-exporter/exporter.ts
2300
- function slotOverrideKey(itemId, slotId) {
2301
- return `${itemId}::${slotId}`;
2302
- }
2303
- function responseColumnId(itemId, slotId) {
2304
- return `${itemId}::${slotId}`;
2305
- }
2306
- function metaColumnId(metaField) {
2307
- return `meta:${metaField}`;
2308
- }
2309
- function contextColumnId(contextKey) {
2310
- return `context:${contextKey}`;
2311
- }
2312
- function formatMetaValue(response, metaField) {
2313
- switch (metaField) {
2314
- case "submittedAt":
2315
- if (response.submittedAt == null || response.submittedAt === 0) return "";
2316
- return format(/* @__PURE__ */ new Date(response.submittedAt * 1e3), "yyyy-MM-dd'T'HH:mm:ss.SSSxxx");
2317
- case "participantId": return response.participantId ?? "";
2318
- case "responseId": return response.key;
2319
- case "versionId": return response.versionId;
2320
- 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
+ };
2321
1038
  }
2322
- }
2323
- var SurveyResponseExporter = class {
2324
- versions = /* @__PURE__ */ new Map();
2325
- constructor(versionBindings, profiles = []) {
2326
- this.profiles = profiles;
2327
- for (const vb of versionBindings) this.versions.set(vb.versionId, vb);
2328
- }
2329
- getSurveyForVersion(versionId) {
2330
- return this.versions.get(versionId);
2331
- }
2332
- /**
2333
- * Compute response-only columns from survey definition.
2334
- */
2335
- computeResponseColumns(versionId) {
2336
- const binding = this.versions.get(versionId);
2337
- if (!binding) return [];
2338
- const survey = binding.survey;
2339
- const keyIndex = survey.keyIndex;
2340
- const keyByItemId = new Map(keyIndex.map((r) => [r.itemId, r]));
2341
- const profile = this.resolveProfileForVersion(versionId);
2342
- const columns = [];
2343
- for (const { itemId, slots } of survey.getResponseSlotDefinitions()) {
2344
- const keyInfo = keyByItemId.get(itemId);
2345
- const itemBaseKey = keyInfo ? [...keyInfo.keyPath, keyInfo.key].join(".") : itemId;
2346
- for (const slot of slots) {
2347
- const columnId = responseColumnId(itemId, slot.slotId);
2348
- const codingPath = [...slot.keyPath, slot.key].filter((part) => part.trim().length > 0);
2349
- const defaultHeader = codingPath.length > 0 ? `${itemBaseKey}__${codingPath.join(".")}` : `${itemBaseKey}__${slot.slotId}`;
2350
- const header = this.resolveColumnHeader(profile, columnId, defaultHeader);
2351
- columns.push({
2352
- id: columnId,
2353
- header,
2354
- source: ExportColumnSource.response,
2355
- itemId,
2356
- slotId: slot.slotId,
2357
- valueType: slot.valueType
2358
- });
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));
2359
1058
  }
2360
- }
2361
- return columns;
2362
- }
2363
- /**
2364
- * Compute full export columns: meta first, then context (from responses), then response columns.
2365
- * Column order: meta (fixed) -> context (profile order or alphabetical) -> response (profile order + alphabetical).
2366
- */
2367
- computeColumns(versionId, responses, versionIds) {
2368
- const vids = versionIds ?? [versionId];
2369
- const responseCols = [];
2370
- const seen = /* @__PURE__ */ new Set();
2371
- for (const vid of vids) for (const c of this.computeResponseColumns(vid)) if (!seen.has(c.id)) {
2372
- seen.add(c.id);
2373
- responseCols.push(c);
2374
- }
2375
- const profile = this.resolveProfileForVersion(versionId);
2376
- const metaColumns = META_COLUMN_ORDER.map((metaField) => ({
2377
- id: metaColumnId(metaField),
2378
- header: this.resolveColumnHeader(profile, metaColumnId(metaField), metaField),
2379
- source: ExportColumnSource.meta,
2380
- metaField
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
+ }];
2381
1069
  }));
2382
- const contextKeys = /* @__PURE__ */ new Set();
2383
- for (const r of responses ?? []) if (r.context) for (const k of Object.keys(r.context)) contextKeys.add(k);
2384
- const contextOrder = profile?.contextColumnOrder;
2385
- const contextCols = Array.from(contextKeys).sort(contextOrder ? (a, b) => {
2386
- const ia = contextOrder.indexOf(a);
2387
- const ib = contextOrder.indexOf(b);
2388
- if (ia >= 0 && ib >= 0) return ia - ib;
2389
- if (ia >= 0) return -1;
2390
- if (ib >= 0) return 1;
2391
- return a.localeCompare(b);
2392
- } : void 0).map((contextKey) => ({
2393
- id: contextColumnId(contextKey),
2394
- header: this.resolveColumnHeader(profile, contextColumnId(contextKey), `context.${contextKey}`),
2395
- source: ExportColumnSource.context,
2396
- contextKey
2397
- }));
2398
- const respOrder = profile?.responseColumnOrder ?? [];
2399
- const byOrder = respOrder.filter((id) => responseCols.some((c) => c.id === id)).map((id) => responseCols.find((c) => c.id === id));
2400
- const remaining = responseCols.filter((c) => !respOrder.includes(c.id)).sort((a, b) => a.header.localeCompare(b.header));
2401
- const respCols = [...byOrder, ...remaining];
2402
- const columns = [
2403
- ...metaColumns,
2404
- ...contextCols,
2405
- ...respCols
2406
- ];
2407
- this.assertUniqueHeaders(columns);
2408
- return columns;
2409
- }
2410
- resolveProfileForVersion(versionId) {
2411
- return this.profiles.find((p) => !p.versionId || p.versionId === versionId);
2412
- }
2413
- resolveColumnHeader(profile, columnId, defaultHeader) {
2414
- const override = profile?.columnHeaders?.[columnId]?.trim();
2415
- return override && override.length > 0 ? override : defaultHeader;
2416
- }
2417
- assertUniqueHeaders(columns) {
2418
- const seen = /* @__PURE__ */ new Map();
2419
- for (const column of columns) {
2420
- const existingId = seen.get(column.header);
2421
- if (existingId && existingId !== column.id) throw new Error(`Duplicate export column header '${column.header}' for columns '${existingId}' and '${column.id}'`);
2422
- seen.set(column.header, column.id);
2423
- }
2424
- }
2425
- getTransformConfig(versionId, itemId, slotId) {
2426
- return this.resolveProfileForVersion(versionId)?.slotOverrides?.[slotOverrideKey(itemId, slotId)] ?? DEFAULT_TRANSFORM;
2427
- }
2428
- getContextTransformConfig(versionId, contextKey) {
2429
- return this.resolveProfileForVersion(versionId)?.contextOverrides?.[contextKey] ?? DEFAULT_TRANSFORM;
2430
1070
  }
2431
- exportResponse(response) {
2432
- if (!this.versions.get(response.versionId)) return null;
2433
- const columns = this.computeColumns(response.versionId, [response]);
2434
- const row = {};
2435
- for (const col of columns) {
2436
- if (col.source === ExportColumnSource.meta && col.metaField) {
2437
- row[col.header] = formatMetaValue(response, col.metaField);
2438
- continue;
2439
- }
2440
- if (col.source === ExportColumnSource.context && col.contextKey) {
2441
- const value = response.context?.[col.contextKey];
2442
- const transformConfig = this.getContextTransformConfig(response.versionId, col.contextKey);
2443
- const valueType = value?.type ?? ValueType.string;
2444
- row[col.header] = exportSingleResponseSlot({
2445
- value,
2446
- valueType,
2447
- 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
2448
1114
  });
2449
- 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);
2450
1118
  }
2451
- if (col.source === ExportColumnSource.response && col.itemId != null && col.slotId != null && col.valueType != null) {
2452
- const value = response.responses.get(col.itemId)?.response?.get(col.slotId);
2453
- const transformConfig = this.getTransformConfig(response.versionId, col.itemId, col.slotId);
2454
- row[col.header] = exportSingleResponseSlot({
2455
- value,
2456
- valueType: col.valueType,
2457
- transformConfig
2458
- });
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);
2459
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)) : "";
2460
1202
  }
2461
1203
  return row;
2462
1204
  }
2463
- /**
2464
- * Compute columns for a batch of responses (union of response columns across versions).
2465
- */
2466
- computeColumnsForBatch(responses) {
2467
- if (responses.length === 0) return [];
2468
- const versionIds = [...new Set(responses.map((r) => r.versionId))];
2469
- const primaryVersionId = versionIds[0];
2470
- 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);
2471
1215
  }
2472
- exportResponsesToCsv(responses, options) {
2473
- if (responses.length === 0) return "";
2474
- 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);
2475
1223
  }
2476
1224
  };
2477
1225
  //#endregion
@@ -2480,20 +1228,35 @@ var SurveyResponseExporter = class {
2480
1228
  * Generate a structured codebook from a survey definition.
2481
1229
  */
2482
1230
  function generateCodebook(survey, options) {
1231
+ const registry = survey.getResponseSlotRegistry();
1232
+ validateExportConfiguration(survey, registry);
1233
+ const slots = registry.definitions;
2483
1234
  const locales = options?.locales ?? survey.locales;
2484
- const keyIndex = survey.keyIndex;
2485
- const keyByItemId = new Map(keyIndex.map((r) => [r.itemId, r]));
2486
- const slotDefsByItemId = new Map(survey.getResponseSlotDefinitions().map(({ itemId, slots }) => [itemId, slots.map((slot) => ({
2487
- slotId: slot.slotId,
2488
- key: slot.key,
2489
- keyPath: [...slot.keyPath],
2490
- valueType: slot.valueType
2491
- }))]));
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
+ }
2492
1258
  const items = [];
2493
1259
  for (const item of survey.surveyItems.values()) {
2494
- const keyInfo = keyByItemId.get(item.id);
2495
- const key = keyInfo?.key ?? item.id;
2496
- const keyPath = keyInfo?.keyPath ?? [];
2497
1260
  const slotList = slotDefsByItemId.get(item.id) ?? [];
2498
1261
  slotList.sort((a, b) => a.slotId.localeCompare(b.slotId));
2499
1262
  let contentByLocale;
@@ -2511,8 +1274,8 @@ function generateCodebook(survey, options) {
2511
1274
  }
2512
1275
  items.push({
2513
1276
  id: item.id,
2514
- key,
2515
- keyPath,
1277
+ editorName: survey.resolveItemEditorName(item.id, locales[0]),
1278
+ breadcrumb: survey.getItemDisplayPath(item.id, locales[0]).breadcrumb,
2516
1279
  type: item.type,
2517
1280
  interactive: item.isInteractive(),
2518
1281
  slots: slotList,
@@ -2521,10 +1284,41 @@ function generateCodebook(survey, options) {
2521
1284
  }
2522
1285
  return {
2523
1286
  surveyKey: survey.surveyKey,
2524
- 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
+ }))
2525
1319
  };
2526
1320
  }
2527
1321
  //#endregion
2528
- 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_EXPRESSION_REFERENCE_DIAGNOSTIC_POLICY, 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, findIntroducedBlockingExpressionReferenceDiagnostics, findIntroducedExpressionReferenceDiagnosticsIncludingAdvisories, flattenTree, generateCodebook, generateCodingKey, generateId, getAssetUsagesFromContent, getContentPlainText, getItemExpressionDefinition, getPlainTextFromRichTextContent, getValueAtJsonPointer, gt, gte, hasRenderableRichTextBlock, hasRenderableRichTextContent, in_range, initValueForType, isBlockingSurveyExpressionReferenceDiagnostic, 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 };
2529
1323
 
2530
1324
  //# sourceMappingURL=index.mjs.map