tina4-nodejs 3.13.149 → 3.13.151

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.
Files changed (60) hide show
  1. package/CLAUDE.md +2 -2
  2. package/package.json +5 -9
  3. package/packages/cli/dist/bin.js +1074 -521
  4. package/packages/core/dist/index.js +1071 -516
  5. package/packages/core/public/bundled-component-provenance.json +2 -2
  6. package/packages/core/public/js/tina4-dev-admin-split.html +202 -0
  7. package/packages/core/public/js/tina4-dev-admin.min.js +147 -138
  8. package/packages/core/src/aiClient.ts +2 -1
  9. package/packages/core/src/api.ts +2 -1
  10. package/packages/core/src/auth.ts +1 -1
  11. package/packages/core/src/cache.ts +6 -1
  12. package/packages/core/src/devAdmin.ts +209 -123
  13. package/packages/core/src/dispatchPipeline.ts +4 -0
  14. package/packages/core/src/docs.ts +5 -3
  15. package/packages/core/src/errorOverlay.ts +18 -9
  16. package/packages/core/src/index.ts +2 -1
  17. package/packages/core/src/mcp.ts +53 -16
  18. package/packages/core/src/messenger.ts +22 -30
  19. package/packages/core/src/metrics.ts +5 -1
  20. package/packages/core/src/middleware.ts +8 -1
  21. package/packages/core/src/optionalPackage.ts +2 -2
  22. package/packages/core/src/plan.ts +4 -4
  23. package/packages/core/src/push.ts +1 -1
  24. package/packages/core/src/request.ts +185 -25
  25. package/packages/core/src/router.ts +3 -2
  26. package/packages/core/src/server.ts +32 -10
  27. package/packages/core/src/session.ts +44 -0
  28. package/packages/core/src/slashTrim.ts +39 -0
  29. package/packages/core/src/wsdl.ts +66 -48
  30. package/packages/core/templates/crud/form.twig +5 -5
  31. package/packages/core/templates/crud/table.twig +2 -2
  32. package/packages/core/templates/errors/302.twig +4 -3
  33. package/packages/frond/dist/index.js +206 -64
  34. package/packages/frond/src/engine.ts +278 -69
  35. package/packages/frond/src/index.ts +1 -1
  36. package/packages/orm/dist/index.js +1055 -502
  37. package/packages/orm/src/crudRender.ts +5 -5
  38. package/packages/orm/src/crudTable.ts +1 -1
  39. package/packages/orm/src/database.ts +15 -29
  40. package/packages/orm/src/databaseResult.ts +11 -1
  41. package/packages/orm/src/realtime/index.ts +1 -0
  42. package/packages/orm/src/realtime/realtime.ts +2 -1
  43. package/packages/orm/src/realtime/s3Client.ts +276 -0
  44. package/packages/orm/src/realtime/storage.ts +19 -60
  45. package/packages/orm/src/sqlTranslator.ts +58 -42
  46. package/packages/swagger/dist/index.js +25 -4
  47. package/packages/swagger/src/generator.ts +7 -4
  48. package/packages/swagger/src/ui.ts +2 -1
  49. package/types/core/src/devAdmin.d.ts +40 -0
  50. package/types/core/src/index.d.ts +2 -1
  51. package/types/core/src/middleware.d.ts +1 -0
  52. package/types/core/src/optionalPackage.d.ts +2 -2
  53. package/types/core/src/request.d.ts +5 -1
  54. package/types/core/src/session.d.ts +25 -0
  55. package/types/core/src/slashTrim.d.ts +6 -0
  56. package/types/frond/src/engine.d.ts +11 -0
  57. package/types/frond/src/index.d.ts +1 -1
  58. package/types/orm/src/realtime/index.d.ts +1 -0
  59. package/types/orm/src/realtime/s3Client.d.ts +48 -0
  60. package/types/orm/src/realtime/storage.d.ts +9 -7
@@ -166,6 +166,14 @@ var FILTER_WITH_ARGS_RE = /^(\w+)\s*\(([\s\S]*)\)$/;
166
166
  var FILTER_COMPARISON_RE = /^(\w+)\s*(!=|==|>=|<=|>|<)\s*(.+)$/;
167
167
  var TITLE_WORD_RE = /\b\w/g;
168
168
  var STRIP_TAGS_RE = /<[^>]+>/g;
169
+ function stripTags(input) {
170
+ let s = input, prev;
171
+ do {
172
+ prev = s;
173
+ s = s.replace(STRIP_TAGS_RE, "");
174
+ } while (s !== prev);
175
+ return s;
176
+ }
169
177
  var FAST_FILTERS = {
170
178
  upper: (value) => String(value).toUpperCase(),
171
179
  lower: (value) => String(value).toLowerCase(),
@@ -180,7 +188,7 @@ var FAST_FILTERS = {
180
188
  int: (value) => value ? parseInt(String(value), 10) || 0 : 0,
181
189
  float: (value) => value ? parseFloat(String(value)) || 0 : 0,
182
190
  abs: (value) => typeof value === "number" ? Math.abs(value) : value,
183
- striptags: (value) => String(value).replace(STRIP_TAGS_RE, ""),
191
+ striptags: (value) => stripTags(String(value)),
184
192
  first: (value) => Array.isArray(value) ? value[0] ?? null : null,
185
193
  last: (value) => Array.isArray(value) ? value[value.length - 1] ?? null : null,
186
194
  keys: (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? Object.keys(value) : [],
@@ -194,14 +202,12 @@ var FAST_FILTERS = {
194
202
  filter: (value) => Array.isArray(value) ? value.filter(Boolean) : value
195
203
  };
196
204
  var FORMAT_RE = /%%|%([-+ 0]*)(\d+)?(?:\.(\d+))?([sdifFeEgGxXob])/g;
197
- var LEADING_WS_RE = /^\s+/;
198
- var TRAILING_WS_RE = /\s+$/;
199
205
  var THOUSANDS_RE = /\B(?=(\d{3})+(?!\d))/g;
200
206
  var LIVE_RE = /^live\s+["']([^"']+)["']([\s\S]*)$/;
201
207
  var LIVE_WS_RE = /ws\s+["']([^"']+)["']/;
202
208
  var LIVE_SRC_RE = /src\s+["']([^"']+)["']/;
203
- var EXTENDS_RE = /\{%[-\s]*extends\s+["'](.+?)["']\s*[-]?%\}/;
204
- var EXTENDS_RE_GLOBAL = /\{%[-\s]*extends\s+["'](.+?)["']\s*[-]?%\}/g;
209
+ var EXTENDS_RE = /\{%[-\s]*extends\s+["']([^"']+)["']\s*[-]?%\}/;
210
+ var EXTENDS_RE_GLOBAL = /\{%[-\s]*extends\s+["']([^"']+)["']\s*[-]?%\}/g;
205
211
  function liveAttr(value) {
206
212
  return String(value).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
207
213
  }
@@ -223,37 +229,65 @@ function sweepExpiredCache(cache) {
223
229
  if (expiresAt <= now) cache.delete(key);
224
230
  }
225
231
  }
226
- var TOKEN_RE = /(\{%-?\s*[\s\S]*?\s*-?%\})|(\{\{-?\s*[\s\S]*?\s*-?\}\})|(\{#[\s\S]*?#\})/g;
227
- var RAW_BLOCK_RE = /\{%-?\s*raw\s*-?%\}([\s\S]*?)\{%-?\s*endraw\s*-?%\}/g;
228
- function tokenize(source) {
232
+ var RAW_OPEN_RE = /\{%-?\s*raw\s*-?%\}/g;
233
+ var RAW_CLOSE_RE = /\{%-?\s*endraw\s*-?%\}/g;
234
+ function extractRawBlocks(source) {
229
235
  const rawBlocks = [];
230
- source = source.replace(RAW_BLOCK_RE, (_match, content) => {
236
+ let out = "";
237
+ let pos = 0;
238
+ RAW_OPEN_RE.lastIndex = 0;
239
+ let om;
240
+ while ((om = RAW_OPEN_RE.exec(source)) !== null) {
241
+ const openStart = om.index;
242
+ const openEnd = RAW_OPEN_RE.lastIndex;
243
+ RAW_CLOSE_RE.lastIndex = openEnd;
244
+ const cm = RAW_CLOSE_RE.exec(source);
245
+ if (cm === null) break;
231
246
  const idx = rawBlocks.length;
232
- rawBlocks.push(content);
233
- return `\0RAW_${idx}\0`;
234
- });
247
+ rawBlocks.push(source.slice(openEnd, cm.index));
248
+ out += source.slice(pos, openStart) + `\0RAW_${idx}\0`;
249
+ pos = RAW_CLOSE_RE.lastIndex;
250
+ RAW_OPEN_RE.lastIndex = pos;
251
+ }
252
+ out += source.slice(pos);
253
+ return { source: out, rawBlocks };
254
+ }
255
+ var TAG_KINDS = {
256
+ "%": { close: "%}", type: "BLOCK" },
257
+ "{": { close: "}}", type: "VAR" },
258
+ "#": { close: "#}", type: "COMMENT" }
259
+ };
260
+ function tokenize(rawSource) {
261
+ const { source, rawBlocks } = extractRawBlocks(rawSource);
235
262
  const tokens = [];
263
+ const n = source.length;
236
264
  let pos = 0;
237
- TOKEN_RE.lastIndex = 0;
238
- let m;
239
- while ((m = TOKEN_RE.exec(source)) !== null) {
240
- const start = m.index;
241
- if (start > pos) {
242
- tokens.push(["TEXT", source.slice(pos, start)]);
243
- }
244
- const raw = m[0];
245
- if (raw.startsWith("{#")) {
246
- tokens.push(["COMMENT", raw]);
247
- } else if (raw.startsWith("{{")) {
248
- tokens.push(["VAR", raw]);
249
- } else if (raw.startsWith("{%")) {
250
- tokens.push(["BLOCK", raw]);
251
- }
252
- pos = m.index + raw.length;
253
- }
254
- if (pos < source.length) {
255
- tokens.push(["TEXT", source.slice(pos)]);
265
+ let scan = 0;
266
+ const exhausted = { "%}": false, "}}": false, "#}": false };
267
+ while (scan < n) {
268
+ const brace = source.indexOf("{", scan);
269
+ if (brace === -1) break;
270
+ const kind = TAG_KINDS[source[brace + 1]];
271
+ if (!kind) {
272
+ scan = brace + 1;
273
+ continue;
274
+ }
275
+ if (exhausted[kind.close]) {
276
+ scan = brace + 1;
277
+ continue;
278
+ }
279
+ const close = source.indexOf(kind.close, brace + 2);
280
+ if (close === -1) {
281
+ exhausted[kind.close] = true;
282
+ scan = brace + 1;
283
+ continue;
284
+ }
285
+ if (brace > pos) tokens.push(["TEXT", source.slice(pos, brace)]);
286
+ tokens.push([kind.type, source.slice(brace, close + 2)]);
287
+ pos = close + 2;
288
+ scan = close + 2;
256
289
  }
290
+ if (pos < n) tokens.push(["TEXT", source.slice(pos)]);
257
291
  if (rawBlocks.length > 0) {
258
292
  for (let i = 0; i < tokens.length; i++) {
259
293
  if (tokens[i][0] === "TEXT" && tokens[i][1].includes("\0RAW_")) {
@@ -589,26 +623,125 @@ var ARITHMETIC_OPERATIONS = {
589
623
  "%": (left, right) => right !== 0 ? left % right : 0,
590
624
  "**": (left, right) => left ** right
591
625
  };
626
+ var ARITHMETIC_LEVELS = [["+", "-"], ["*", "//", "/", "%"], ["**"]];
627
+ function topLevelPositions(expr) {
628
+ const positions = [];
629
+ let inQuote = null;
630
+ let depth = 0;
631
+ for (let pos = 0; pos < expr.length; pos++) {
632
+ const ch = expr[pos];
633
+ if (inQuote) {
634
+ if (ch === inQuote) inQuote = null;
635
+ } else if (ch === '"' || ch === "'") {
636
+ inQuote = ch;
637
+ } else if ("([{".includes(ch)) {
638
+ depth++;
639
+ } else if (")]}".includes(ch)) {
640
+ depth--;
641
+ } else if (depth === 0) {
642
+ positions.push(pos);
643
+ }
644
+ }
645
+ return positions;
646
+ }
647
+ function followsOperand(expr, pos) {
648
+ return /[\w)\]'"]$/.test(expr.slice(0, pos).trimEnd());
649
+ }
650
+ function topLevelArithmeticOperators(expr) {
651
+ const operators = [];
652
+ let skipUntil = -1;
653
+ for (const pos of topLevelPositions(expr)) {
654
+ if (pos < skipUntil || !"+-*/%".includes(expr[pos])) continue;
655
+ const pair = expr.slice(pos, pos + 2);
656
+ const op = pair === "**" || pair === "//" ? pair : expr[pos];
657
+ skipUntil = pos + op.length;
658
+ if (followsOperand(expr, pos)) operators.push([pos, op]);
659
+ }
660
+ return operators;
661
+ }
662
+ function splitArithmetic(expr) {
663
+ const operators = topLevelArithmeticOperators(expr);
664
+ for (const level of ARITHMETIC_LEVELS) {
665
+ const matches = operators.filter(([, op]) => level.includes(op));
666
+ if (matches.length > 0) return level[0] === "**" ? matches[0] : matches[matches.length - 1];
667
+ }
668
+ return null;
669
+ }
592
670
  function evalArithmeticExpression(expr, context) {
593
- for (const op of [" + ", " - ", " * ", " // ", " / ", " % ", " ** "]) {
594
- const pos = findOutsideQuotes(expr, op);
595
- if (pos >= 0) {
596
- const left = expr.slice(0, pos).trim();
597
- const right = expr.slice(pos + op.length).trim();
598
- const lVal = evalExpr(left, context);
599
- const rVal = evalExpr(right, context);
600
- try {
601
- let lNum = lVal != null ? Number(lVal) : 0;
602
- let rNum = rVal != null ? Number(rVal) : 0;
603
- if (isNaN(lNum)) lNum = 0;
604
- if (isNaN(rNum)) rNum = 0;
605
- return ARITHMETIC_OPERATIONS[op.trim()](lNum, rNum);
606
- } catch {
607
- return null;
608
- }
671
+ const split = splitArithmetic(expr);
672
+ if (!split) return EXPR_NOT_MATCHED;
673
+ const [pos, op] = split;
674
+ const lVal = evalExpr(expr.slice(0, pos).trim(), context);
675
+ const rVal = evalExpr(expr.slice(pos + op.length).trim(), context);
676
+ let lNum = lVal != null ? Number(lVal) : 0;
677
+ let rNum = rVal != null ? Number(rVal) : 0;
678
+ if (isNaN(lNum)) lNum = 0;
679
+ if (isNaN(rNum)) rNum = 0;
680
+ return ARITHMETIC_OPERATIONS[op](lNum, rNum);
681
+ }
682
+ function frondRange(start, end, step = 1) {
683
+ const numeric = (value) => typeof value === "number" || typeof value === "string" && NUMERIC_RE.test(value);
684
+ if (!numeric(start) || !numeric(end) || !numeric(step)) return [];
685
+ const first = Math.trunc(Number(start));
686
+ const last = Math.trunc(Number(end));
687
+ const stride = Math.abs(Math.trunc(Number(step)));
688
+ if (stride === 0) return [];
689
+ const values = [];
690
+ if (first <= last) for (let value = first; value <= last; value += stride) values.push(value);
691
+ else for (let value = first; value >= last; value -= stride) values.push(value);
692
+ return values;
693
+ }
694
+ function findRangeOperator(expr) {
695
+ let pos = findOutsideQuotes(expr, "..");
696
+ while (pos >= 0) {
697
+ const partOfEllipsis = expr.startsWith("...", pos) || pos > 0 && expr[pos - 1] === ".";
698
+ if (pos > 0 && !partOfEllipsis) return findOutsideQuotes(expr.slice(pos + 2), "|") >= 0 ? -1 : pos;
699
+ const next = findOutsideQuotes(expr.slice(pos + 2), "..");
700
+ pos = next >= 0 ? pos + 2 + next : -1;
701
+ }
702
+ return -1;
703
+ }
704
+ function evalRangeExpression(expr, context) {
705
+ const pos = findRangeOperator(expr);
706
+ if (pos < 0) return EXPR_NOT_MATCHED;
707
+ return frondRange(evalExpr(expr.slice(0, pos).trim(), context), evalExpr(expr.slice(pos + 2).trim(), context));
708
+ }
709
+ function closingBracketIndex(expr) {
710
+ let depth = 0;
711
+ let inQuote = null;
712
+ for (let i = 0; i < expr.length; i++) {
713
+ const ch = expr[i];
714
+ if (inQuote) {
715
+ if (ch === inQuote) inQuote = null;
716
+ } else if (ch === '"' || ch === "'") {
717
+ inQuote = ch;
718
+ } else if ("([{".includes(ch)) {
719
+ depth++;
720
+ } else if (")]}".includes(ch) && --depth === 0) {
721
+ return i;
609
722
  }
610
723
  }
611
- return EXPR_NOT_MATCHED;
724
+ return -1;
725
+ }
726
+ var BARE_KEY_RE = /^[A-Za-z_]\w*$/;
727
+ function mapLiteralKey(rawKey, context) {
728
+ const quoted = rawKey.length >= 2 && (rawKey[0] === '"' || rawKey[0] === "'") && rawKey.endsWith(rawKey[0]);
729
+ if (quoted) return rawKey.slice(1, -1);
730
+ return BARE_KEY_RE.test(rawKey) ? rawKey : String(evalExpr(rawKey, context));
731
+ }
732
+ function evalMapLiteral(inner, context) {
733
+ const map = {};
734
+ for (const pair of inner === "" ? [] : splitArgs(inner)) {
735
+ const colon = findOutsideQuotes(pair, ":");
736
+ if (colon > 0) map[mapLiteralKey(pair.slice(0, colon).trim(), context)] = evalExpr(pair.slice(colon + 1).trim(), context);
737
+ }
738
+ return map;
739
+ }
740
+ function evalCollectionLiteral(expr, context) {
741
+ if (expr[0] !== "[" && expr[0] !== "{" || closingBracketIndex(expr) !== expr.length - 1) return EXPR_NOT_MATCHED;
742
+ const inner = expr.slice(1, -1).trim();
743
+ if (expr[0] === "{") return evalMapLiteral(inner, context);
744
+ return inner === "" ? [] : splitArgs(inner).map((item) => evalExpr(item.trim(), context));
612
745
  }
613
746
  function evalFilterExpression(expr, context) {
614
747
  if (findOutsideQuotes(expr, "|") >= 0) {
@@ -655,9 +788,11 @@ function evalFunctionExpression(expr, context) {
655
788
  return EXPR_NOT_MATCHED;
656
789
  }
657
790
  var EXPR_EVALUATORS = [
791
+ evalCollectionLiteral,
658
792
  evalPrimary,
659
793
  evalConditional,
660
794
  evalConcatOrComparison,
795
+ evalRangeExpression,
661
796
  evalArithmeticExpression,
662
797
  evalFilterExpression,
663
798
  evalFunctionExpression
@@ -1045,12 +1180,12 @@ function splitArgs(raw) {
1045
1180
  current += ch;
1046
1181
  continue;
1047
1182
  }
1048
- if (ch === "(" || ch === "[") {
1183
+ if (ch === "(" || ch === "[" || ch === "{") {
1049
1184
  depth++;
1050
1185
  current += ch;
1051
1186
  continue;
1052
1187
  }
1053
- if (ch === ")" || ch === "]") {
1188
+ if (ch === ")" || ch === "]" || ch === "}") {
1054
1189
  depth--;
1055
1190
  current += ch;
1056
1191
  continue;
@@ -1258,8 +1393,8 @@ var BUILTIN_FILTERS = {
1258
1393
  },
1259
1394
  title: (v) => String(v).replace(TITLE_WORD_RE, (c) => c.toUpperCase()),
1260
1395
  trim: (v) => String(v).trim(),
1261
- ltrim: (v) => String(v).replace(LEADING_WS_RE, ""),
1262
- rtrim: (v) => String(v).replace(TRAILING_WS_RE, ""),
1396
+ ltrim: (v) => String(v).trimStart(),
1397
+ rtrim: (v) => String(v).trimEnd(),
1263
1398
  length: (v) => {
1264
1399
  if (Array.isArray(v)) return v.length;
1265
1400
  if (typeof v === "string") return v.length;
@@ -1300,7 +1435,7 @@ var BUILTIN_FILTERS = {
1300
1435
  safe: (v) => v,
1301
1436
  escape: (v, ...a) => escapeStrategy(v, a[0] ?? "html"),
1302
1437
  e: (v, ...a) => escapeStrategy(v, a[0] ?? "html"),
1303
- striptags: (v) => String(v).replace(STRIP_TAGS_RE, ""),
1438
+ striptags: (v) => stripTags(String(v)),
1304
1439
  nl2br: (v) => new SafeString(htmlEscape(String(v)).replace(/\n/g, "<br />\n")),
1305
1440
  abs: (v) => typeof v === "number" ? Math.abs(v) : v,
1306
1441
  round: (v, decimals) => {
@@ -1413,9 +1548,13 @@ var BUILTIN_FILTERS = {
1413
1548
  js_escape: (v) => new SafeString(jsEscape(v))
1414
1549
  };
1415
1550
  function _b64url(data) {
1416
- return data.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1551
+ return data.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/={1,2}$/, "");
1417
1552
  }
1418
1553
  var _formTokenSessionId = "";
1554
+ var _formTokenSessionProvider = () => "";
1555
+ function setFormTokenSessionProvider(fn) {
1556
+ _formTokenSessionProvider = fn;
1557
+ }
1419
1558
  var _cspNonceProvider = () => "";
1420
1559
  function setCspNonceProvider(fn) {
1421
1560
  _cspNonceProvider = fn;
@@ -1441,8 +1580,9 @@ function _buildFormTokenJwt(descriptor = "") {
1441
1580
  payload.context = descriptor;
1442
1581
  }
1443
1582
  }
1444
- if (_formTokenSessionId) {
1445
- payload.session_id = _formTokenSessionId;
1583
+ const sessionId = _formTokenSessionProvider() || _formTokenSessionId;
1584
+ if (sessionId) {
1585
+ payload.session_id = sessionId;
1446
1586
  }
1447
1587
  const h = _b64url(Buffer.from(JSON.stringify(header)));
1448
1588
  const p = _b64url(Buffer.from(JSON.stringify(payload)));
@@ -1610,6 +1750,7 @@ var Frond = class _Frond {
1610
1750
  this.globals.form_token = (descriptor) => _generateFormToken(descriptor || "");
1611
1751
  this.globals.formTokenValue = (descriptor) => _generateFormTokenValue(descriptor || "");
1612
1752
  this.globals.form_token_value = (descriptor) => _generateFormTokenValue(descriptor || "");
1753
+ this.globals.range = frondRange;
1613
1754
  this.globals.dump = (value) => renderDump(value);
1614
1755
  this.globals.csp_nonce = () => _cspNonceProvider();
1615
1756
  for (const [k, v] of _Frond.classFilters) this.filters[k] = v;
@@ -1946,20 +2087,20 @@ var Frond = class _Frond {
1946
2087
  }
1947
2088
  renderVarToken(tokens, index, context, output) {
1948
2089
  const [content, stripB, stripA] = stripTag(tokens[index][1]);
1949
- if (stripB && output.length > 0) output[output.length - 1] = output[output.length - 1].replace(TRAILING_WS_RE, "");
2090
+ if (stripB && output.length > 0) output[output.length - 1] = output[output.length - 1].trimEnd();
1950
2091
  const result = this.evalVar(content, context);
1951
2092
  output.push(result !== null && result !== void 0 ? String(result) : "");
1952
2093
  if (stripA && index + 1 < tokens.length && tokens[index + 1][0] === "TEXT") {
1953
- tokens[index + 1] = ["TEXT", tokens[index + 1][1].replace(LEADING_WS_RE, "")];
2094
+ tokens[index + 1] = ["TEXT", tokens[index + 1][1].trimStart()];
1954
2095
  }
1955
2096
  return index + 1;
1956
2097
  }
1957
2098
  renderBlockToken(tokens, index, context, output) {
1958
2099
  const [content, stripB, stripA] = stripTag(tokens[index][1]);
1959
- if (stripB && output.length > 0) output[output.length - 1] = output[output.length - 1].replace(TRAILING_WS_RE, "");
2100
+ if (stripB && output.length > 0) output[output.length - 1] = output[output.length - 1].trimEnd();
1960
2101
  const tag = content.split(/\s+/)[0] || "";
1961
2102
  if (stripA && index + 1 < tokens.length && tokens[index + 1][0] === "TEXT") {
1962
- tokens[index + 1] = ["TEXT", tokens[index + 1][1].replace(LEADING_WS_RE, "")];
2103
+ tokens[index + 1] = ["TEXT", tokens[index + 1][1].trimStart()];
1963
2104
  }
1964
2105
  let next;
1965
2106
  if (tag === "if" && this.tagPermitted(tag)) {
@@ -1976,7 +2117,7 @@ var Frond = class _Frond {
1976
2117
  next = block.next;
1977
2118
  }
1978
2119
  if (stripA && next < tokens.length && tokens[next][0] === "TEXT") {
1979
- tokens[next] = ["TEXT", tokens[next][1].replace(LEADING_WS_RE, "")];
2120
+ tokens[next] = ["TEXT", tokens[next][1].trimStart()];
1980
2121
  }
1981
2122
  return next;
1982
2123
  }
@@ -2235,7 +2376,7 @@ var Frond = class _Frond {
2235
2376
  pushIfBranch(branches, condition, branchTokens, stripBefore) {
2236
2377
  if (stripBefore && branchTokens.length > 0 && branchTokens[branchTokens.length - 1][0] === "TEXT") {
2237
2378
  const last = branchTokens[branchTokens.length - 1];
2238
- branchTokens[branchTokens.length - 1] = ["TEXT", last[1].replace(TRAILING_WS_RE, "")];
2379
+ branchTokens[branchTokens.length - 1] = ["TEXT", last[1].trimEnd()];
2239
2380
  }
2240
2381
  branches.push([condition, branchTokens]);
2241
2382
  }
@@ -2263,7 +2404,7 @@ var Frond = class _Frond {
2263
2404
  } else if (tag === "endif") {
2264
2405
  this.pushIfBranch(branches, currentCond, currentTokens, tagStripB);
2265
2406
  if (tagStripA && i + 1 < tokens.length && tokens[i + 1][0] === "TEXT") {
2266
- tokens[i + 1] = ["TEXT", tokens[i + 1][1].replace(LEADING_WS_RE, "")];
2407
+ tokens[i + 1] = ["TEXT", tokens[i + 1][1].trimStart()];
2267
2408
  }
2268
2409
  return { branches, next: i + 1 };
2269
2410
  } else if ((tag === "elseif" || tag === "elif") && depth === 0) {
@@ -2839,5 +2980,6 @@ var Frond = class _Frond {
2839
2980
  export {
2840
2981
  Frond,
2841
2982
  escapeHtml,
2842
- setCspNonceProvider
2983
+ setCspNonceProvider,
2984
+ setFormTokenSessionProvider
2843
2985
  };