@streamline-pulse/formkrafter-core 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1006 @@
1
+ // lib/utils/localized-text.ts
2
+ function isLocalizedObject(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length > 0 && Object.values(value).every((entry) => typeof entry === "string");
4
+ }
5
+ function resolveLocalizedText(value, locale) {
6
+ if (!isLocalizedObject(value))
7
+ return value;
8
+ if (locale && value[locale] !== undefined)
9
+ return value[locale];
10
+ return Object.values(value)[0];
11
+ }
12
+ function resolveLocalizedRecord(record, locale) {
13
+ if (!record)
14
+ return record;
15
+ const resolved = {};
16
+ for (const [key, value] of Object.entries(record)) {
17
+ resolved[key] = resolveLocalizedText(value, locale);
18
+ }
19
+ return resolved;
20
+ }
21
+
22
+ // lib/validators/default-messages.ts
23
+ var DEFAULT_MESSAGES = {
24
+ en: {
25
+ required: "This field is required",
26
+ minLength: "Must be at least {value} characters",
27
+ maxLength: "Must be at most {value} characters",
28
+ min: "Must be at least {value}",
29
+ max: "Must be at most {value}",
30
+ pattern: "Invalid format",
31
+ minItems: "Select at least {value} item(s)",
32
+ maxItems: "Select at most {value} item(s)",
33
+ email: "Invalid email address",
34
+ url: "Invalid URL",
35
+ type: "Invalid value",
36
+ custom: "Invalid value"
37
+ },
38
+ fr: {
39
+ required: "Ce champ est obligatoire",
40
+ minLength: "Minimum {value} caractères",
41
+ maxLength: "Maximum {value} caractères",
42
+ min: "Minimum {value}",
43
+ max: "Maximum {value}",
44
+ pattern: "Format invalide",
45
+ minItems: "Sélectionnez au moins {value} élément(s)",
46
+ maxItems: "Sélectionnez au plus {value} élément(s)",
47
+ email: "Adresse email invalide",
48
+ url: "URL invalide",
49
+ type: "Valeur invalide",
50
+ custom: "Valeur invalide"
51
+ }
52
+ };
53
+ function registerValidationMessages(locale, messages) {
54
+ DEFAULT_MESSAGES[locale] = { ...DEFAULT_MESSAGES[locale], ...messages };
55
+ }
56
+ function defaultValidationMessage(validator, locale, value) {
57
+ const pack = DEFAULT_MESSAGES[locale ?? "en"] ?? DEFAULT_MESSAGES.en;
58
+ const template = pack[validator] ?? DEFAULT_MESSAGES.en[validator] ?? "Invalid value";
59
+ return template.replace("{value}", String(value ?? ""));
60
+ }
61
+
62
+ // lib/utils/brick-spec.ts
63
+ function* iterateBricks(spec, path = "0") {
64
+ if (!spec)
65
+ return;
66
+ yield { brick: spec, path };
67
+ for (const [index, child] of (spec.children ?? []).entries()) {
68
+ yield* iterateBricks(child, `${path}.${index}`);
69
+ }
70
+ }
71
+ function* iterateSchemaBricks(spec) {
72
+ if (!spec)
73
+ return;
74
+ yield spec;
75
+ if (spec.type === "collection")
76
+ return;
77
+ for (const child of spec.children ?? []) {
78
+ yield* iterateSchemaBricks(child);
79
+ }
80
+ }
81
+ function buildValidationSchema(brickSpecs, locale) {
82
+ if (!brickSpecs)
83
+ return;
84
+ const properties = {};
85
+ const required = [];
86
+ const requiredErrorMessage = {};
87
+ const messageOf = (raw) => {
88
+ const resolved = resolveLocalizedText(raw, locale);
89
+ return typeof resolved === "string" ? resolved : undefined;
90
+ };
91
+ for (const brick of iterateSchemaBricks(brickSpecs)) {
92
+ const key = brick.configs?.key;
93
+ const dataType = brick.dataType;
94
+ if (!key || !dataType || dataType === "void")
95
+ continue;
96
+ const isRequired = brick.validations?.find((validation2) => validation2.validator === "required");
97
+ const property = {
98
+ type: dataType,
99
+ nullable: !isRequired
100
+ };
101
+ const messages = {
102
+ type: defaultValidationMessage("type", locale)
103
+ };
104
+ for (const validation2 of brick.validations ?? []) {
105
+ const { validator, value } = validation2;
106
+ const message = messageOf(validation2.message) ?? defaultValidationMessage(validator, locale, value);
107
+ if (validator === "minLength" && value != null) {
108
+ property.minLength = Number(value);
109
+ messages.minLength = message;
110
+ } else if (validator === "maxLength" && value != null) {
111
+ property.maxLength = Number(value);
112
+ messages.maxLength = message;
113
+ } else if (validator === "min" && value != null) {
114
+ property.minimum = Number(value);
115
+ messages.minimum = message;
116
+ } else if (validator === "max" && value != null) {
117
+ property.maximum = Number(value);
118
+ messages.maximum = message;
119
+ } else if (validator === "pattern" && typeof value === "string" && value) {
120
+ property.pattern = value;
121
+ messages.pattern = message;
122
+ } else if (validator === "minItems" && value != null) {
123
+ property.minItems = Number(value);
124
+ messages.minItems = message;
125
+ } else if (validator === "maxItems" && value != null) {
126
+ property.maxItems = Number(value);
127
+ messages.maxItems = message;
128
+ } else if (validator === "email") {
129
+ property.format = "email";
130
+ messages.format = message;
131
+ } else if (validator === "url") {
132
+ property.format = "uri";
133
+ messages.format = message;
134
+ }
135
+ }
136
+ property.errorMessage = messages;
137
+ properties[key] = property;
138
+ if (isRequired) {
139
+ required.push(key);
140
+ requiredErrorMessage[key] = messageOf(isRequired.message) ?? defaultValidationMessage("required", locale);
141
+ }
142
+ }
143
+ return {
144
+ type: "object",
145
+ properties,
146
+ required,
147
+ ...Object.keys(requiredErrorMessage).length ? {
148
+ errorMessage: {
149
+ required: requiredErrorMessage
150
+ }
151
+ } : {},
152
+ additionalProperties: false
153
+ };
154
+ }
155
+ // lib/brick/brick.ts
156
+ class Brick {
157
+ type;
158
+ dataType;
159
+ id;
160
+ name;
161
+ configsForm;
162
+ category;
163
+ styles;
164
+ unWrapData;
165
+ isPrivate;
166
+ validators;
167
+ defaultConfigs;
168
+ constructor(params) {
169
+ this.type = params.type;
170
+ this.dataType = params.dataType;
171
+ this.id = params.id;
172
+ this.name = params.name;
173
+ this.configsForm = params.configsForm;
174
+ this.category = params.category;
175
+ this.styles = params.styles;
176
+ this.unWrapData = params.unWrapData ?? false;
177
+ this.isPrivate = params.isPrivate ?? false;
178
+ this.validators = params.validators ?? [];
179
+ this.defaultConfigs = params.defaultConfigs;
180
+ }
181
+ getBrickEmptySpec() {
182
+ return {
183
+ type: this.type,
184
+ dataType: this.dataType,
185
+ id: this.id,
186
+ name: this.name,
187
+ ...this.category !== undefined ? { category: this.category } : {},
188
+ ...this.configsForm !== undefined ? { configsForm: this.configsForm } : {},
189
+ ...this.styles !== undefined ? { styles: this.styles } : {},
190
+ ...this.defaultConfigs !== undefined ? { configs: { ...this.defaultConfigs } } : {},
191
+ validators: this.validators
192
+ };
193
+ }
194
+ }
195
+ // lib/brick/utils.ts
196
+ import jsonLogic from "json-logic-js";
197
+
198
+ // lib/services/js_runner_service.ts
199
+ import { parse as parse2 } from "acorn";
200
+
201
+ // lib/services/sandbox_interpreter.ts
202
+ import { parse } from "acorn";
203
+ var FORBIDDEN_PROPS = new Set(["__proto__", "constructor", "prototype"]);
204
+ var MAX_STEPS = 1e5;
205
+
206
+ class ReturnSignal {
207
+ value;
208
+ constructor(value) {
209
+ this.value = value;
210
+ }
211
+ }
212
+ var lookup = (ctx, name) => {
213
+ for (let i = ctx.scopes.length - 1;i >= 0; i--) {
214
+ if (ctx.scopes[i].has(name))
215
+ return ctx.scopes[i].get(name);
216
+ }
217
+ throw new Error(`Unknown identifier "${name}"`);
218
+ };
219
+ var declareVar = (ctx, name, value) => {
220
+ ctx.scopes[ctx.scopes.length - 1].set(name, value);
221
+ };
222
+ var assign = (ctx, name, value) => {
223
+ for (let i = ctx.scopes.length - 1;i >= 0; i--) {
224
+ if (ctx.scopes[i].has(name)) {
225
+ ctx.scopes[i].set(name, value);
226
+ return;
227
+ }
228
+ }
229
+ throw new Error(`Cannot assign to unknown identifier "${name}"`);
230
+ };
231
+ var guardProp = (prop) => {
232
+ if (typeof prop === "number")
233
+ return prop;
234
+ const name = String(prop);
235
+ if (FORBIDDEN_PROPS.has(name)) {
236
+ throw new Error(`Access to "${name}" is not allowed`);
237
+ }
238
+ return name;
239
+ };
240
+ var memberTarget = (ctx, node) => {
241
+ const object = evalNode(ctx, node.object);
242
+ const prop = node.computed ? guardProp(evalNode(ctx, node.property)) : guardProp(node.property.name);
243
+ return { object, prop, optional: node.optional === true };
244
+ };
245
+ var readMember = (ctx, node) => {
246
+ const { object, prop, optional } = memberTarget(ctx, node);
247
+ if (object === null || object === undefined) {
248
+ if (optional)
249
+ return;
250
+ throw new Error(`Cannot read "${String(prop)}" of ${String(object)}`);
251
+ }
252
+ return object[prop];
253
+ };
254
+ var evalNode = (ctx, node) => {
255
+ if (++ctx.steps > MAX_STEPS) {
256
+ throw new Error("Sandbox execution budget exceeded");
257
+ }
258
+ switch (node.type) {
259
+ case "Program":
260
+ case "BlockStatement": {
261
+ for (const statement of node.body) {
262
+ const result = evalNode(ctx, statement);
263
+ if (result instanceof ReturnSignal)
264
+ return result;
265
+ }
266
+ return;
267
+ }
268
+ case "ReturnStatement":
269
+ return new ReturnSignal(node.argument ? evalNode(ctx, node.argument) : undefined);
270
+ case "IfStatement": {
271
+ if (evalNode(ctx, node.test)) {
272
+ return evalNode(ctx, node.consequent);
273
+ }
274
+ if (node.alternate)
275
+ return evalNode(ctx, node.alternate);
276
+ return;
277
+ }
278
+ case "VariableDeclaration": {
279
+ for (const declaration of node.declarations) {
280
+ const id = declaration.id;
281
+ if (id.type !== "Identifier") {
282
+ throw new Error("Only simple variable names are supported");
283
+ }
284
+ declareVar(ctx, id.name, declaration.init ? evalNode(ctx, declaration.init) : undefined);
285
+ }
286
+ return;
287
+ }
288
+ case "ExpressionStatement":
289
+ evalNode(ctx, node.expression);
290
+ return;
291
+ case "Literal":
292
+ return node.value;
293
+ case "TemplateLiteral": {
294
+ const quasis = node.quasis;
295
+ const expressions = node.expressions;
296
+ let text = "";
297
+ quasis.forEach((quasi, index) => {
298
+ text += quasi.value.cooked ?? "";
299
+ if (index < expressions.length) {
300
+ text += String(evalNode(ctx, expressions[index]));
301
+ }
302
+ });
303
+ return text;
304
+ }
305
+ case "Identifier":
306
+ return lookup(ctx, node.name);
307
+ case "ThisExpression":
308
+ return;
309
+ case "ChainExpression":
310
+ return evalNode(ctx, node.expression);
311
+ case "MemberExpression":
312
+ return readMember(ctx, node);
313
+ case "CallExpression": {
314
+ const callee = node.callee;
315
+ const args = [];
316
+ for (const argument of node.arguments) {
317
+ if (argument.type === "SpreadElement") {
318
+ const spread = evalNode(ctx, argument.argument);
319
+ if (Array.isArray(spread))
320
+ args.push(...spread);
321
+ } else {
322
+ args.push(evalNode(ctx, argument));
323
+ }
324
+ }
325
+ if (callee.type === "MemberExpression") {
326
+ const { object, prop, optional } = memberTarget(ctx, callee);
327
+ if (object === null || object === undefined) {
328
+ if (optional || node.optional === true)
329
+ return;
330
+ throw new Error(`Cannot call "${String(prop)}" of ${String(object)}`);
331
+ }
332
+ const fn3 = object[prop];
333
+ if (typeof fn3 !== "function") {
334
+ if (node.optional === true && fn3 == null)
335
+ return;
336
+ throw new Error(`"${String(prop)}" is not a function`);
337
+ }
338
+ if (fn3 === Function || fn3 === eval) {
339
+ throw new Error("This function is not allowed");
340
+ }
341
+ return fn3.apply(object, args);
342
+ }
343
+ const fn2 = evalNode(ctx, callee);
344
+ if (typeof fn2 !== "function") {
345
+ if (node.optional === true && fn2 == null)
346
+ return;
347
+ throw new Error("Value is not a function");
348
+ }
349
+ if (fn2 === Function || fn2 === eval) {
350
+ throw new Error("This function is not allowed");
351
+ }
352
+ return fn2(...args);
353
+ }
354
+ case "ArrowFunctionExpression": {
355
+ const params = node.params;
356
+ const body = node.body;
357
+ const capturedScopes = [...ctx.scopes];
358
+ return (...args) => {
359
+ const callScope = new Map;
360
+ params.forEach((param, index) => {
361
+ if (param.type !== "Identifier") {
362
+ throw new Error("Only simple parameters are supported");
363
+ }
364
+ callScope.set(param.name, args[index]);
365
+ });
366
+ const callCtx = {
367
+ steps: ctx.steps,
368
+ scopes: [...capturedScopes, callScope]
369
+ };
370
+ const result = evalNode(callCtx, body);
371
+ ctx.steps = callCtx.steps;
372
+ if (result instanceof ReturnSignal)
373
+ return result.value;
374
+ return node.expression === true ? result : undefined;
375
+ };
376
+ }
377
+ case "BinaryExpression": {
378
+ const left = evalNode(ctx, node.left);
379
+ const right = evalNode(ctx, node.right);
380
+ switch (node.operator) {
381
+ case "===":
382
+ return left === right;
383
+ case "!==":
384
+ return left !== right;
385
+ case "==":
386
+ return left == right;
387
+ case "!=":
388
+ return left != right;
389
+ case "<":
390
+ return left < right;
391
+ case "<=":
392
+ return left <= right;
393
+ case ">":
394
+ return left > right;
395
+ case ">=":
396
+ return left >= right;
397
+ case "+":
398
+ return left + right;
399
+ case "-":
400
+ return left - right;
401
+ case "*":
402
+ return left * right;
403
+ case "/":
404
+ return left / right;
405
+ case "%":
406
+ return left % right;
407
+ case "**":
408
+ return left ** right;
409
+ default:
410
+ throw new Error(`Unsupported operator "${String(node.operator)}"`);
411
+ }
412
+ }
413
+ case "LogicalExpression": {
414
+ const left = evalNode(ctx, node.left);
415
+ switch (node.operator) {
416
+ case "&&":
417
+ return left && evalNode(ctx, node.right);
418
+ case "||":
419
+ return left || evalNode(ctx, node.right);
420
+ case "??":
421
+ return left ?? evalNode(ctx, node.right);
422
+ default:
423
+ throw new Error(`Unsupported operator "${String(node.operator)}"`);
424
+ }
425
+ }
426
+ case "UnaryExpression": {
427
+ const argument = evalNode(ctx, node.argument);
428
+ switch (node.operator) {
429
+ case "!":
430
+ return !argument;
431
+ case "-":
432
+ return -argument;
433
+ case "+":
434
+ return +argument;
435
+ case "typeof":
436
+ return typeof argument;
437
+ default:
438
+ throw new Error(`Unsupported operator "${String(node.operator)}"`);
439
+ }
440
+ }
441
+ case "ConditionalExpression":
442
+ return evalNode(ctx, node.test) ? evalNode(ctx, node.consequent) : evalNode(ctx, node.alternate);
443
+ case "AssignmentExpression": {
444
+ const target = node.left;
445
+ if (target.type !== "Identifier" || node.operator !== "=") {
446
+ throw new Error("Only simple assignments are supported");
447
+ }
448
+ const value = evalNode(ctx, node.right);
449
+ assign(ctx, target.name, value);
450
+ return value;
451
+ }
452
+ case "ArrayExpression": {
453
+ const values = [];
454
+ for (const element of node.elements) {
455
+ if (!element)
456
+ continue;
457
+ if (element.type === "SpreadElement") {
458
+ const spread = evalNode(ctx, element.argument);
459
+ if (Array.isArray(spread))
460
+ values.push(...spread);
461
+ } else {
462
+ values.push(evalNode(ctx, element));
463
+ }
464
+ }
465
+ return values;
466
+ }
467
+ case "ObjectExpression": {
468
+ const result = {};
469
+ for (const property of node.properties) {
470
+ if (property.type === "SpreadElement") {
471
+ const spread = evalNode(ctx, property.argument);
472
+ if (spread && typeof spread === "object") {
473
+ for (const [key2, value] of Object.entries(spread)) {
474
+ result[String(guardProp(key2))] = value;
475
+ }
476
+ }
477
+ continue;
478
+ }
479
+ const keyNode = property.key;
480
+ const key = property.computed ? guardProp(evalNode(ctx, keyNode)) : guardProp(keyNode.type === "Identifier" ? keyNode.name : keyNode.value);
481
+ result[String(key)] = evalNode(ctx, property.value);
482
+ }
483
+ return result;
484
+ }
485
+ default:
486
+ throw new Error(`Unsupported syntax: ${node.type}`);
487
+ }
488
+ };
489
+ var SAFE_BUILTINS = {
490
+ undefined: undefined,
491
+ NaN: NaN,
492
+ Infinity: Infinity,
493
+ Math,
494
+ JSON,
495
+ Number,
496
+ String,
497
+ Boolean,
498
+ Array,
499
+ Object,
500
+ Date,
501
+ parseInt,
502
+ parseFloat,
503
+ isNaN,
504
+ isFinite
505
+ };
506
+ function runSandboxed(code, globals = {}) {
507
+ const ast = parse(code, {
508
+ ecmaVersion: "latest",
509
+ allowReturnOutsideFunction: true
510
+ });
511
+ const ctx = {
512
+ steps: 0,
513
+ scopes: [
514
+ new Map(Object.entries({ ...SAFE_BUILTINS, ...globals })),
515
+ new Map
516
+ ]
517
+ };
518
+ const result = evalNode(ctx, ast);
519
+ return result instanceof ReturnSignal ? result.value : undefined;
520
+ }
521
+
522
+ // lib/services/js_runner_service.ts
523
+ class SandboxJsRunnerService {
524
+ validateJs(js2, options = { ecmaVersion: "latest", allowReturnOutsideFunction: true }) {
525
+ try {
526
+ parse2(js2, options);
527
+ return { valide: true };
528
+ } catch (error) {
529
+ return {
530
+ valide: false,
531
+ error: error instanceof Error ? error : new Error(String(error))
532
+ };
533
+ }
534
+ }
535
+ eval(js2, scope2 = {}) {
536
+ return runSandboxed(js2, scope2);
537
+ }
538
+ }
539
+ var JsRunnerServiceImplementation = SandboxJsRunnerService;
540
+
541
+ // lib/services/data_source_service.ts
542
+ class FetchDataSourceService {
543
+ defaults;
544
+ cache = new Map;
545
+ constructor(defaults = {}) {
546
+ this.defaults = defaults;
547
+ }
548
+ fetchOptions(url, options) {
549
+ const headers = { ...this.defaults.headers, ...options?.headers };
550
+ const init = {};
551
+ if (Object.keys(headers).length)
552
+ init.headers = headers;
553
+ if (this.defaults.credentials)
554
+ init.credentials = this.defaults.credentials;
555
+ const key = `${url}|${JSON.stringify(init)}`;
556
+ let pending = this.cache.get(key);
557
+ if (!pending) {
558
+ pending = fetch(url, Object.keys(init).length ? init : undefined).then((response) => {
559
+ if (!response.ok) {
560
+ throw new Error(`Data source request failed (${response.status}): ${url}`);
561
+ }
562
+ return response.json();
563
+ }).then((json) => Array.isArray(json) ? json : []).catch((error) => {
564
+ this.cache.delete(key);
565
+ throw error;
566
+ });
567
+ this.cache.set(key, pending);
568
+ }
569
+ return pending;
570
+ }
571
+ }
572
+
573
+ // lib/services/file_upload_service.ts
574
+ class Base64FileUploadService {
575
+ async upload(file) {
576
+ const encode = globalThis.btoa;
577
+ if (!encode)
578
+ throw new Error("No base64 encoder available");
579
+ const bytes = new Uint8Array(await file.arrayBuffer());
580
+ let binary = "";
581
+ for (const byte of bytes)
582
+ binary += String.fromCharCode(byte);
583
+ return {
584
+ name: file.name,
585
+ type: file.type,
586
+ size: file.size,
587
+ url: `data:${file.type};base64,${encode(binary)}`
588
+ };
589
+ }
590
+ }
591
+
592
+ // lib/services/index.ts
593
+ var GLOBAL_KEY = Symbol.for("formkrafter.core.services");
594
+ var globalStore = globalThis;
595
+ var services = globalStore[GLOBAL_KEY] ??= {
596
+ jsRunnerService: new SandboxJsRunnerService,
597
+ dataSourceService: new FetchDataSourceService,
598
+ fileUploadService: new Base64FileUploadService
599
+ };
600
+
601
+ // lib/brick/utils.ts
602
+ var evalBrickCode = (code, data) => {
603
+ try {
604
+ return services.jsRunnerService.eval(code, { dataMap: data ?? {} });
605
+ } catch (error) {
606
+ return error instanceof Error ? error : new Error(String(error));
607
+ }
608
+ };
609
+ var evalRule = (rule, dataMap) => {
610
+ if (rule.type === "jsonLogic") {
611
+ if (rule.logic == null)
612
+ return false;
613
+ return jsonLogic.apply(rule.logic, dataMap) === true;
614
+ }
615
+ if (rule.type === "javaScript") {
616
+ if (!rule.code)
617
+ return false;
618
+ return evalBrickCode(rule.code, dataMap) === true;
619
+ }
620
+ return false;
621
+ };
622
+ var getActiveRules = (rules, dataMap) => rules?.filter((rule) => evalRule(rule, dataMap));
623
+ var getActiveEffect = (rules, dataMap) => getActiveRules(rules, dataMap)?.map((rule) => rule.effects).flat().filter((effect) => effect != null);
624
+ var getAffectedProperties = (rules, dataMap) => {
625
+ return getActiveEffect(rules, dataMap)?.map((effect) => {
626
+ let value;
627
+ if (effect.property) {
628
+ if (effect.property.type === "boolean")
629
+ value = effect.boolean;
630
+ else if (effect.property.type === "logic" && effect.logic != null)
631
+ value = jsonLogic.apply(effect.logic, dataMap);
632
+ else if (effect.property.type === "code" && effect.code)
633
+ value = evalBrickCode(effect.code, dataMap);
634
+ else if (effect.property.type === "string")
635
+ value = effect.string;
636
+ return {
637
+ [effect.property.target]: value
638
+ };
639
+ }
640
+ return {};
641
+ }).flat().reduce((acc, item) => ({ ...acc, ...item }), {}) ?? { hidden: false };
642
+ };
643
+ // lib/validators/validator.ts
644
+ import { Ajv } from "ajv";
645
+ import ajvErrors from "ajv-errors";
646
+ import addFormats from "ajv-formats";
647
+ var ajv = new Ajv({ allErrors: true });
648
+ addFormats(ajv, ["email", "uri"]);
649
+ ajvErrors(ajv);
650
+ var validatorCache = new WeakMap;
651
+ var compiledValidator = (brickSpec, locale) => {
652
+ let byLocale = validatorCache.get(brickSpec);
653
+ if (!byLocale) {
654
+ byLocale = new Map;
655
+ validatorCache.set(brickSpec, byLocale);
656
+ }
657
+ const cacheKey = locale ?? "";
658
+ let validate = byLocale.get(cacheKey);
659
+ if (!validate) {
660
+ const schema = buildValidationSchema(brickSpec, locale);
661
+ if (!schema)
662
+ return;
663
+ try {
664
+ validate = ajv.compile(schema);
665
+ } catch {
666
+ return;
667
+ }
668
+ byLocale.set(cacheKey, validate);
669
+ }
670
+ return validate;
671
+ };
672
+ var customErrors = (brickSpec, data, locale) => {
673
+ const errors = {};
674
+ const dataMap = data ?? {};
675
+ for (const brick of iterateSchemaBricks(brickSpec)) {
676
+ const key = brick.configs?.key;
677
+ if (!key)
678
+ continue;
679
+ for (const validation2 of brick.validations ?? []) {
680
+ if (validation2.validator !== "custom" || !validation2.customValidator) {
681
+ continue;
682
+ }
683
+ const result = evalBrickCode(`const value = dataMap?.[${JSON.stringify(key)}];
684
+ ${validation2.customValidator}`, dataMap);
685
+ if (result === true || result === undefined || result === null)
686
+ continue;
687
+ if (key in errors)
688
+ continue;
689
+ if (typeof result === "string")
690
+ errors[key] = result;
691
+ else if (result instanceof Error)
692
+ errors[key] = result.message;
693
+ else {
694
+ const resolved = resolveLocalizedText(validation2.message, locale);
695
+ errors[key] = typeof resolved === "string" ? resolved : defaultValidationMessage("custom", locale);
696
+ }
697
+ }
698
+ }
699
+ return errors;
700
+ };
701
+ var keyOfError = (error) => {
702
+ if (error.keyword === "required") {
703
+ return error.params.missingProperty;
704
+ }
705
+ if (error.keyword === "errorMessage") {
706
+ const inner = error.params.errors?.[0];
707
+ return inner ? keyOfError(inner) : undefined;
708
+ }
709
+ return error.instancePath.split("/").filter(Boolean)[0];
710
+ };
711
+ var validateBrickSpecDataDetailed = (brickSpec, data, locale) => {
712
+ const errors = {};
713
+ const validate = compiledValidator(brickSpec, locale);
714
+ if (validate && !validate(data)) {
715
+ for (const error of validate.errors ?? []) {
716
+ const key = keyOfError(error);
717
+ if (key && !(key in errors) && error.message) {
718
+ errors[key] = error.message;
719
+ }
720
+ }
721
+ }
722
+ for (const [key, message] of Object.entries(customErrors(brickSpec, data, locale))) {
723
+ if (!(key in errors))
724
+ errors[key] = message;
725
+ }
726
+ return { valid: Object.keys(errors).length === 0, errors };
727
+ };
728
+ var validateBrickSpecData = (brickSpec, data, locale) => validateBrickSpecDataDetailed(brickSpec, data, locale).valid;
729
+ var stripEmpty = (data) => {
730
+ if (typeof data !== "object" || data === null)
731
+ return {};
732
+ return Object.fromEntries(Object.entries(data).filter(([, value]) => value !== "" && value !== null && value !== undefined));
733
+ };
734
+ var rowSpecCache = new WeakMap;
735
+ var rowSpecOf = (collection) => {
736
+ let rowSpec = rowSpecCache.get(collection);
737
+ if (!rowSpec) {
738
+ rowSpec = {
739
+ type: "panel",
740
+ id: "collection-row",
741
+ name: "Row",
742
+ configs: { key: `${collection.configs?.key ?? "collection"}_row` },
743
+ children: collection.children ?? []
744
+ };
745
+ rowSpecCache.set(collection, rowSpec);
746
+ }
747
+ return rowSpec;
748
+ };
749
+ var validateFormData = (brickSpec, data, locale) => {
750
+ const present = stripEmpty(data);
751
+ const errors = {
752
+ ...validateBrickSpecDataDetailed(brickSpec, present, locale).errors
753
+ };
754
+ for (const brick of iterateSchemaBricks(brickSpec)) {
755
+ if (brick.type !== "collection")
756
+ continue;
757
+ const key = brick.configs?.key;
758
+ if (!key)
759
+ continue;
760
+ const rows = present[key];
761
+ if (!Array.isArray(rows))
762
+ continue;
763
+ const rowSpec = rowSpecOf(brick);
764
+ rows.forEach((row, index) => {
765
+ const rowResult = validateFormData(rowSpec, row, locale);
766
+ for (const [field, message] of Object.entries(rowResult.errors)) {
767
+ const prefixed = `${key}[${index}].${field}`;
768
+ if (!(prefixed in errors))
769
+ errors[prefixed] = message;
770
+ }
771
+ });
772
+ }
773
+ return { valid: Object.keys(errors).length === 0, errors };
774
+ };
775
+ // lib/services/unsafe_js_runner_service.ts
776
+ import { parse as parse3 } from "acorn";
777
+
778
+ class UnsafeEvalJsRunnerService {
779
+ validateJs(js2, options = { ecmaVersion: "latest", allowReturnOutsideFunction: true }) {
780
+ try {
781
+ parse3(js2, options);
782
+ return { valide: true };
783
+ } catch (error) {
784
+ return {
785
+ valide: false,
786
+ error: error instanceof Error ? error : new Error(String(error))
787
+ };
788
+ }
789
+ }
790
+ eval(js, scope = {}) {
791
+ const prelude = Object.entries(scope).map(([name, value]) => `const ${name} = ${JSON.stringify(value)};`).join(`
792
+ `);
793
+ const fn = `(() => {${prelude}
794
+ ${js}})();`;
795
+ const validation = this.validateJs(fn, { ecmaVersion: "latest" });
796
+ if (!validation.valide)
797
+ throw validation.error;
798
+ return eval(fn);
799
+ }
800
+ }
801
+ // lib/ops/pointer.ts
802
+ function pointerFromPath(path) {
803
+ return path.split(".").slice(1).map((segment) => `/children/${segment}`).join("");
804
+ }
805
+ function pointerOfUid(spec, uid) {
806
+ if (spec.configs?.uid === uid)
807
+ return "";
808
+ for (const [index, child] of (spec.children ?? []).entries()) {
809
+ const childPointer = pointerOfUid(child, uid);
810
+ if (childPointer !== undefined)
811
+ return `/children/${index}${childPointer}`;
812
+ }
813
+ return;
814
+ }
815
+ function getBrickAt(spec, path) {
816
+ let current = spec;
817
+ for (const segment of path.split(".").slice(1)) {
818
+ current = current?.children?.[Number(segment)];
819
+ }
820
+ return current;
821
+ }
822
+ // lib/utils/fast-json-patch.ts
823
+ import * as fastJsonPatchModule from "fast-json-patch";
824
+ var fastJsonPatch = fastJsonPatchModule.default ?? fastJsonPatchModule;
825
+ var applyPatch = fastJsonPatch.applyPatch;
826
+
827
+ // lib/ops/ops.ts
828
+ var apply = (spec, patches, inverse) => ({
829
+ spec: applyPatch(spec, patches, true, false).newDocument,
830
+ patches,
831
+ inverse
832
+ });
833
+ var requireBrickAt = (spec, path) => {
834
+ const brick = getBrickAt(spec, path);
835
+ if (!brick)
836
+ throw new Error(`No brick at path "${path}"`);
837
+ return brick;
838
+ };
839
+ var requireBrickByUid = (spec, uid) => {
840
+ const pointer = pointerOfUid(spec, uid);
841
+ if (pointer === undefined)
842
+ throw new Error(`No brick with uid "${uid}"`);
843
+ let brick = spec;
844
+ for (const segment of pointer.split("/children/").slice(1)) {
845
+ brick = brick.children[Number(segment)];
846
+ }
847
+ return { pointer, brick };
848
+ };
849
+ var toJsonValue = (value) => JSON.parse(JSON.stringify(value));
850
+ function addBrick(spec, brick, parentPath, index) {
851
+ const parent = requireBrickAt(spec, parentPath);
852
+ const parentPointer = pointerFromPath(parentPath);
853
+ const value = toJsonValue(brick);
854
+ if (!parent.children) {
855
+ const pointer2 = `${parentPointer}/children`;
856
+ return apply(spec, [{ op: "add", path: pointer2, value: [value] }], [{ op: "remove", path: pointer2 }]);
857
+ }
858
+ const position = Math.min(index ?? parent.children.length, parent.children.length);
859
+ const pointer = `${parentPointer}/children/${position}`;
860
+ return apply(spec, [{ op: "add", path: pointer, value }], [{ op: "remove", path: pointer }]);
861
+ }
862
+ function removeBrick(spec, path) {
863
+ if (path === "0")
864
+ throw new Error("Cannot remove the root brick");
865
+ const brick = requireBrickAt(spec, path);
866
+ const pointer = pointerFromPath(path);
867
+ return apply(spec, [{ op: "remove", path: pointer }], [{ op: "add", path: pointer, value: brick }]);
868
+ }
869
+ function moveBrick(spec, from, to) {
870
+ if (from === "0" || to === "0")
871
+ throw new Error("Cannot move the root brick");
872
+ const brick = requireBrickAt(spec, from);
873
+ const fromSegments = from.split(".").map(Number);
874
+ const toSegments = to.split(".").map(Number);
875
+ const parentLength = fromSegments.length - 1;
876
+ const fromIndex = fromSegments[parentLength];
877
+ const sharesFromParent = toSegments.length > parentLength && fromSegments.slice(0, parentLength).every((segment, i) => segment === toSegments[i]);
878
+ const adjusted = [...toSegments];
879
+ if (sharesFromParent) {
880
+ if (adjusted[parentLength] === fromIndex && adjusted.length > fromSegments.length) {
881
+ throw new Error(`Cannot move a brick into its own subtree ("${from}" → "${to}")`);
882
+ }
883
+ if (adjusted[parentLength] > fromIndex)
884
+ adjusted[parentLength] -= 1;
885
+ }
886
+ const fromPointer = pointerFromPath(from);
887
+ const toPointer = pointerFromPath(adjusted.join("."));
888
+ return apply(spec, [
889
+ { op: "remove", path: fromPointer },
890
+ { op: "add", path: toPointer, value: brick }
891
+ ], [
892
+ { op: "remove", path: toPointer },
893
+ { op: "add", path: fromPointer, value: brick }
894
+ ]);
895
+ }
896
+ var withFreshUids = (brick) => ({
897
+ ...brick,
898
+ ...brick.configs ? { configs: { ...brick.configs, uid: crypto.randomUUID() } } : {},
899
+ ...brick.children ? { children: brick.children.map(withFreshUids) } : {}
900
+ });
901
+ function duplicateBrick(spec, path) {
902
+ if (path === "0")
903
+ throw new Error("Cannot duplicate the root brick");
904
+ const brick = requireBrickAt(spec, path);
905
+ const segments = path.split(".");
906
+ const nextIndex = Number(segments[segments.length - 1]) + 1;
907
+ const pointer = pointerFromPath([...segments.slice(0, -1), String(nextIndex)].join("."));
908
+ const copy = withFreshUids(toJsonValue(brick));
909
+ return apply(spec, [{ op: "add", path: pointer, value: copy }], [{ op: "remove", path: pointer }]);
910
+ }
911
+ var setBrickSection = (spec, brickPointer, section, value, previous) => {
912
+ const pointer = `${brickPointer}/${section}`;
913
+ if (previous === undefined) {
914
+ return apply(spec, [{ op: "add", path: pointer, value }], [{ op: "remove", path: pointer }]);
915
+ }
916
+ return apply(spec, [{ op: "replace", path: pointer, value }], [{ op: "replace", path: pointer, value: previous }]);
917
+ };
918
+ function updateBrickConfigs(spec, uid, configs) {
919
+ const { pointer, brick } = requireBrickByUid(spec, uid);
920
+ const merged = { ...brick.configs, ...configs };
921
+ return setBrickSection(spec, pointer, "configs", merged, brick.configs);
922
+ }
923
+ function updateBrickStyles(spec, uid, styles) {
924
+ const { pointer, brick } = requireBrickByUid(spec, uid);
925
+ const merged = { ...brick.styles, ...styles };
926
+ return setBrickSection(spec, pointer, "styles", merged, brick.styles);
927
+ }
928
+ function updateBrickValidations(spec, uid, validations) {
929
+ const { pointer, brick } = requireBrickByUid(spec, uid);
930
+ return setBrickSection(spec, pointer, "validations", validations, brick.validations);
931
+ }
932
+ function updateBrickRules(spec, uid, rules) {
933
+ const { pointer, brick } = requireBrickByUid(spec, uid);
934
+ return setBrickSection(spec, pointer, "rules", rules, brick.rules);
935
+ }
936
+ // lib/ops/history.ts
937
+ class SpecHistory {
938
+ past = [];
939
+ future = [];
940
+ record(update) {
941
+ this.past.push({ patches: update.patches, inverse: update.inverse });
942
+ this.future = [];
943
+ }
944
+ get canUndo() {
945
+ return this.past.length > 0;
946
+ }
947
+ get canRedo() {
948
+ return this.future.length > 0;
949
+ }
950
+ undo(spec) {
951
+ const entry = this.past.pop();
952
+ if (!entry)
953
+ return;
954
+ this.future.push(entry);
955
+ return applyPatch(spec, entry.inverse, true, false).newDocument;
956
+ }
957
+ redo(spec) {
958
+ const entry = this.future.pop();
959
+ if (!entry)
960
+ return;
961
+ this.past.push(entry);
962
+ return applyPatch(spec, entry.patches, true, false).newDocument;
963
+ }
964
+ clear() {
965
+ this.past = [];
966
+ this.future = [];
967
+ }
968
+ }
969
+ export {
970
+ validateFormData,
971
+ validateBrickSpecDataDetailed,
972
+ validateBrickSpecData,
973
+ updateBrickValidations,
974
+ updateBrickStyles,
975
+ updateBrickRules,
976
+ updateBrickConfigs,
977
+ services,
978
+ runSandboxed,
979
+ resolveLocalizedText,
980
+ resolveLocalizedRecord,
981
+ removeBrick,
982
+ registerValidationMessages,
983
+ pointerOfUid,
984
+ pointerFromPath,
985
+ moveBrick,
986
+ iterateSchemaBricks,
987
+ iterateBricks,
988
+ isLocalizedObject,
989
+ getBrickAt,
990
+ getAffectedProperties,
991
+ evalBrickCode,
992
+ duplicateBrick,
993
+ defaultValidationMessage,
994
+ buildValidationSchema,
995
+ addBrick,
996
+ UnsafeEvalJsRunnerService,
997
+ SpecHistory,
998
+ SandboxJsRunnerService,
999
+ JsRunnerServiceImplementation,
1000
+ FetchDataSourceService,
1001
+ Brick,
1002
+ Base64FileUploadService
1003
+ };
1004
+
1005
+ //# debugId=C472894AC5683A1F64756E2164756E21
1006
+ //# sourceMappingURL=index.js.map