@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/README.md +148 -23
- package/build/editor/colors.d.mts +2 -12
- package/build/editor/colors.mjs.map +1 -1
- package/build/editor.d.mts +74 -17
- package/build/editor.d.mts.map +1 -1
- package/build/editor.mjs +445 -106
- package/build/editor.mjs.map +1 -1
- package/build/index-SDbZ5WQM.d.mts +2208 -0
- package/build/index-SDbZ5WQM.d.mts.map +1 -0
- package/build/index.d.mts +3 -932
- package/build/index.mjs +605 -1811
- package/build/index.mjs.map +1 -1
- package/build/item-colors-DgoJeSCQ.d.mts +12 -0
- package/build/item-colors-DgoJeSCQ.d.mts.map +1 -0
- package/build/package.json +7 -8
- package/build/projection-Bl6qCinx.mjs +3930 -0
- package/build/projection-Bl6qCinx.mjs.map +1 -0
- package/package.json +7 -8
- package/build/editor/colors.d.mts.map +0 -1
- package/build/index.d.mts.map +0 -1
- package/build/survey-BKblV5rN.mjs +0 -1460
- package/build/survey-BKblV5rN.mjs.map +0 -1
- package/build/survey-DedDemSc.d.mts +0 -997
- package/build/survey-DedDemSc.d.mts.map +0 -1
package/build/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { $ as
|
|
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
|
-
|
|
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/
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
*/
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
-
|
|
643
|
-
|
|
644
|
-
const
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
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
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
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
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
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
|
|
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
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1675
|
-
|
|
1676
|
-
return Object.keys(
|
|
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
|
-
},
|
|
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(
|
|
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
|
|
522
|
+
changedInPass = this.applyConfiguredPrefillsForItem(item) || changedInPass;
|
|
1773
523
|
});
|
|
1774
524
|
if (!changedInPass) break;
|
|
1775
525
|
}
|
|
1776
|
-
this.evalTemplateValues(
|
|
526
|
+
this.evalTemplateValues(this.createEvaluator());
|
|
1777
527
|
}
|
|
1778
|
-
applyConfiguredPrefillsForItem(item
|
|
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
|
-
|
|
1794
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1903
|
-
|
|
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(
|
|
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:
|
|
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(
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
if (
|
|
1944
|
-
|
|
1945
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
2177
|
-
/**
|
|
2178
|
-
|
|
2179
|
-
"
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
"
|
|
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
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
}
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
}
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
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
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
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
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
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
|
-
|
|
2432
|
-
if (!
|
|
2433
|
-
|
|
2434
|
-
const
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2452
|
-
const
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
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
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
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
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
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
|
|
2485
|
-
const
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
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
|
-
|
|
2515
|
-
|
|
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
|
|
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,
|
|
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
|