@aperturesyndicate/synx-format 3.6.1 → 3.6.3

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 CHANGED
@@ -1,4 +1,6 @@
1
- # SYNX for JS/TS — @aperturesyndicate/synx-format
1
+ > Main SYNX site: https://synx.aperturesyndicate.com/
2
+
3
+ # SYNX for JS/TS � @aperturesyndicate/synx-format
2
4
 
3
5
  The official JavaScript & TypeScript parser for the SYNX format.
4
6
 
@@ -40,10 +42,10 @@ console.log(data.server.port); // typed as number
40
42
 
41
43
  | Method | Description |
42
44
  |---|---|
43
- | `Synx.parse<T>(text, options?)` | Parse a .synx string object |
45
+ | `Synx.parse<T>(text, options?)` | Parse a .synx string ? object |
44
46
  | `Synx.loadSync<T>(path, options?)` | Load & parse file (sync) |
45
47
  | `Synx.load<T>(path, options?)` | Load & parse file (async, returns Promise) |
46
- | `Synx.stringify(obj, active?)` | Serialize object .synx string |
48
+ | `Synx.stringify(obj, active?)` | Serialize object ? .synx string |
47
49
 
48
50
  ### Options
49
51
 
@@ -127,8 +129,8 @@ volume[min:0, max:100, type:int] 75
127
129
 
128
130
  ## Other Languages
129
131
 
130
- - **Python** [synx-format](https://pypi.org/project/synx-format/) on PyPI
131
- - **Rust** [synx](https://crates.io/crates/synx) on crates.io
132
+ - **Python** [synx-format](https://pypi.org/project/synx-format/) on PyPI
133
+ - **Rust** [synx](https://crates.io/crates/synx) on crates.io
132
134
  ```bash
133
135
  cargo add synx
134
136
  ```
@@ -141,4 +143,4 @@ volume[min:0, max:100, type:int] 75
141
143
 
142
144
  ## License
143
145
 
144
- MIT © APERTURESyndicate
146
+ MIT APERTURESyndicate
package/bin/synx.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
4
  const fs = require('fs');
package/dist/calc.js CHANGED
@@ -49,7 +49,7 @@ function tokenize(expr) {
49
49
  i++;
50
50
  continue;
51
51
  }
52
- throw new Error(`SYNX :calc — unexpected character: '${ch}' in expression "${expr}"`);
52
+ throw new Error(`SYNX :calc — unexpected character: '${ch}' in expression`);
53
53
  }
54
54
  return tokens;
55
55
  }
package/dist/calc.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"calc.js","sourceRoot":"","sources":["../src/calc.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"calc.js","sourceRoot":"","sources":["../src/calc.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAA0B,WAAW,EAAe,MAAM,SAAS,CAAC;AAgF5F,wBAAgB,OAAO,CACrB,GAAG,EAAE,UAAU,EACf,OAAO,GAAE,WAAgB,EACzB,IAAI,CAAC,EAAE,UAAU,EACjB,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACrC,aAAa,SAAI,EACjB,YAAY,SAAK,GAChB,UAAU,CAoeZ"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAA0B,WAAW,EAAe,MAAM,SAAS,CAAC;AAuG5F,wBAAgB,OAAO,CACrB,GAAG,EAAE,UAAU,EACf,OAAO,GAAE,WAAgB,EACzB,IAAI,CAAC,EAAE,UAAU,EACjB,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACrC,aAAa,SAAI,EACjB,YAAY,SAAK,GAChB,UAAU,CAujBZ"}
package/dist/engine.js CHANGED
@@ -25,19 +25,29 @@ const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
25
25
  const DEFAULT_MAX_INCLUDE_DEPTH = 16;
26
26
  /** Maximum object nesting depth for active-mode resolution (prevents stack overflow). */
27
27
  const MAX_RESOLVE_DEPTH = 512;
28
- /** Ensure a file path stays inside the base directory (path jail). */
28
+ /** Ensure a file path stays inside the base directory (path jail).
29
+ * Error messages are kept in lock-step with the Rust engine's `jail_path`. */
29
30
  function jailPath(base, filePath) {
30
31
  if (!pathModule)
31
32
  throw new Error('path module not available');
32
- // Block absolute paths
33
+ // Always check leading "/" or "\" first so the message is portable: POSIX
34
+ // absolute paths and Windows rooted paths both produce the same string.
35
+ const first = filePath.charCodeAt(0);
36
+ if (first === 0x2f /* / */ || first === 0x5c /* \ */) {
37
+ throw new Error(`SECURITY: rooted paths are not allowed: '${filePath}'`);
38
+ }
33
39
  if (pathModule.isAbsolute(filePath)) {
34
- throw new Error(`absolute path not allowed: ${filePath}`);
40
+ throw new Error(`SECURITY: absolute paths are not allowed: '${filePath}'`);
35
41
  }
36
42
  const resolved = pathModule.resolve(base, filePath);
37
43
  const normalizedBase = pathModule.resolve(base);
38
- // Ensure resolved path starts with the base directory
44
+ if (filePath.includes('..') &&
45
+ !resolved.startsWith(normalizedBase + pathModule.sep) &&
46
+ resolved !== normalizedBase) {
47
+ throw new Error(`SECURITY: path traversal detected: '${filePath}'`);
48
+ }
39
49
  if (!resolved.startsWith(normalizedBase + pathModule.sep) && resolved !== normalizedBase) {
40
- throw new Error(`path escapes base directory: ${filePath}`);
50
+ throw new Error(`SECURITY: path escapes base directory: '${filePath}'`);
41
51
  }
42
52
  return resolved;
43
53
  }
@@ -72,6 +82,20 @@ class SynxSecret {
72
82
  }
73
83
  }
74
84
  const SPAM_BUCKETS = new Map();
85
+ /**
86
+ * Normalise Node.js fs error messages to OS-agnostic strings so that
87
+ * INCLUDE_ERR / WATCH_ERR text matches the Rust engine. Without this, the
88
+ * surface depends on the host OS ("ENOENT: no such file or directory" on Linux,
89
+ * "ENOENT…" + Windows variant) — diff-noisy and not portable.
90
+ */
91
+ function fmtIoErr(err, ctx) {
92
+ const code = err && err.code;
93
+ if (code === 'ENOENT')
94
+ return `file not found: ${ctx}`;
95
+ if (code === 'EACCES' || code === 'EPERM')
96
+ return `permission denied: ${ctx}`;
97
+ return err && err.message ? err.message : String(err);
98
+ }
75
99
  // ─── Engine ───────────────────────────────────────────────
76
100
  function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _currentPath = '') {
77
101
  if (!root) {
@@ -84,8 +108,11 @@ function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _curre
84
108
  delete obj[k];
85
109
  }
86
110
  // ── Load !include directives ──
87
- if (!includesMap && options._includes) {
88
- includesMap = loadIncludes(options._includes, options);
111
+ if (!includesMap) {
112
+ const inc = options._includes;
113
+ includesMap = inc
114
+ ? loadIncludes(inc, options)
115
+ : new Map();
89
116
  }
90
117
  }
91
118
  // Guard: prevent stack overflow from deeply nested objects
@@ -140,10 +167,10 @@ function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _curre
140
167
  obj[key] = resolvedTarget;
141
168
  }
142
169
  }
143
- // ── :include ──
144
- if (markers.includes('include') && typeof obj[key] === 'string') {
170
+ // ── :include / :import ──
171
+ if ((markers.includes('include') || markers.includes('import')) && typeof obj[key] === 'string') {
145
172
  if (!fs || !pathModule) {
146
- obj[key] = 'INCLUDE_ERR: :include is not supported in browser';
173
+ obj[key] = 'INCLUDE_ERR: :include/:import is not supported in browser';
147
174
  continue;
148
175
  }
149
176
  const maxDepth = options.maxIncludeDepth ?? DEFAULT_MAX_INCLUDE_DEPTH;
@@ -163,29 +190,36 @@ function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _curre
163
190
  resolve(included, { ...options, basePath: pathModule.dirname(fullPath), _includeDepth: currentDepth + 1 }, root);
164
191
  }
165
192
  obj[key] = included;
193
+ // Register the loaded subtree under the key as an alias so
194
+ // `{leaf:<key>}` interpolation finds it (parity with !include directives).
195
+ if (includesMap) {
196
+ includesMap.set(key, included);
197
+ }
166
198
  }
167
199
  catch (e) {
168
- obj[key] = `INCLUDE_ERR: ${e.message}`;
200
+ obj[key] = `INCLUDE_ERR: ${fmtIoErr(e, includePath)}`;
169
201
  }
170
202
  continue;
171
203
  }
172
204
  // ── :env ──
173
- if (markers.includes('env')) {
174
- const varName = String(value);
205
+ // Only resolve when the value is the string variable name. Mirrors the
206
+ // Rust engine's `if let Some(Value::String(var_name)) = map.get(key)` guard
207
+ // so that `key:env:default:foo` with no value (which is parsed as a group)
208
+ // keeps its `{}` shape instead of accidentally applying the default.
209
+ if (markers.includes('env') && typeof value === 'string') {
210
+ const varName = value;
175
211
  const envSource = options.env || (typeof process !== 'undefined' ? process.env : {});
176
212
  const envVal = envSource[varName];
177
- // Check for :default in the marker chain
178
213
  const defaultIdx = markers.indexOf('default');
179
- // Check if key has (string) type hint — if so, skip auto-detection
180
214
  const forceString = metaMap[key]?.typeHint === 'string';
181
215
  if (envVal !== undefined && envVal !== '') {
182
- obj[key] = forceString ? envVal : (isNaN(Number(envVal)) ? envVal : Number(envVal));
216
+ obj[key] = forceString ? envVal : castPrimitive(envVal);
183
217
  }
184
218
  else if (defaultIdx !== -1 && markers.length > defaultIdx + 1) {
185
219
  // :env:default:VALUE — join all parts after 'default' back with ':'
186
220
  // to preserve IPs (0.0.0.0) and compound values
187
221
  const fallback = markers.slice(defaultIdx + 1).join(':');
188
- obj[key] = forceString ? fallback : (isNaN(Number(fallback)) ? fallback : Number(fallback));
222
+ obj[key] = forceString ? fallback : castPrimitive(fallback);
189
223
  }
190
224
  else {
191
225
  obj[key] = null;
@@ -241,12 +275,34 @@ function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _curre
241
275
  }
242
276
  // ── :i18n ──
243
277
  // Selects a localized value from a nested object based on options.lang.
244
- // Syntax: name:i18n\n en Plains\n ru Равнины
278
+ // Plain syntax: name:i18n\n en Plains\n ru Равнины
279
+ // Plural syntax: title:i18n:item_count\n en\n one {count} item\n other {count} items\n ru\n one ...\n few ...\n many ...\n other ...
245
280
  if (markers.includes('i18n') && obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
246
281
  const translations = obj[key];
247
282
  const lang = options.lang || 'en';
248
- const val = translations[lang] ?? translations['en'] ?? Object.values(translations)[0] ?? null;
249
- obj[key] = val;
283
+ let val = translations[lang] ?? translations['en'] ?? Object.values(translations)[0] ?? null;
284
+ const i18nIdx = markers.indexOf('i18n');
285
+ const countField = markers[i18nIdx + 1];
286
+ // Pluralisation: when the picked language entry is itself an object
287
+ // (one/few/many/other), choose by count.
288
+ if (countField && val && typeof val === 'object' && !Array.isArray(val)) {
289
+ const pluralForms = val;
290
+ const countRaw = (deepGet(root, countField) ?? deepGet(obj, countField));
291
+ const count = typeof countRaw === 'number' ? countRaw
292
+ : typeof countRaw === 'string' && !isNaN(Number(countRaw)) ? Number(countRaw)
293
+ : 0;
294
+ const category = pluralCategory(lang, Math.trunc(count));
295
+ const chosen = pluralForms[category] ?? pluralForms['other'] ?? Object.values(pluralForms)[0] ?? null;
296
+ if (typeof chosen === 'string') {
297
+ obj[key] = chosen.split('{count}').join(String(Math.trunc(count)));
298
+ }
299
+ else {
300
+ obj[key] = chosen;
301
+ }
302
+ }
303
+ else {
304
+ obj[key] = val;
305
+ }
250
306
  }
251
307
  // ── :calc ──
252
308
  if (markers.includes('calc') && typeof obj[key] === 'string') {
@@ -271,6 +327,8 @@ function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _curre
271
327
  // Substitute whole-word occurrences without building RegExp objects
272
328
  if (vars.size > 0)
273
329
  expr = replaceVars(expr, vars);
330
+ // Substitute dot-path references (e.g. base.hp, server.port) before evaluating.
331
+ expr = replaceDotPaths(expr, root);
274
332
  try {
275
333
  obj[key] = (0, calc_1.safeCalc)(expr);
276
334
  }
@@ -305,7 +363,11 @@ function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _curre
305
363
  const targetHasAlias = targetParentMeta?.[targetLeafKey]?.markers?.includes('alias') ?? false;
306
364
  const isCycle = targetHasAlias && typeof targetVal === 'string' && targetVal === key;
307
365
  if (isCycle) {
308
- obj[key] = `ALIAS_ERR: circular alias detected: ${key} ${target}`;
366
+ // Stable, order-independent message: sort participants lexicographically
367
+ // so both keys produce the same string regardless of iteration order.
368
+ const a = currentKeyPath <= target ? currentKeyPath : target;
369
+ const b = currentKeyPath <= target ? target : currentKeyPath;
370
+ obj[key] = `ALIAS_ERR: circular alias detected: ${a} → ${b}`;
309
371
  }
310
372
  else {
311
373
  obj[key] = targetVal ?? null;
@@ -368,7 +430,7 @@ function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _curre
368
430
  if (defaultIdx !== -1 && markers.length > defaultIdx + 1) {
369
431
  const fallback = markers.slice(defaultIdx + 1).join(':');
370
432
  const forceStr = metaMap[key]?.typeHint === 'string';
371
- obj[key] = forceStr ? fallback : (isNaN(Number(fallback)) ? fallback : Number(fallback));
433
+ obj[key] = forceStr ? fallback : castPrimitive(fallback);
372
434
  }
373
435
  }
374
436
  }
@@ -420,6 +482,54 @@ function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _curre
420
482
  const pattern = markers[idx + 1] ?? '%s';
421
483
  obj[key] = applyFormatPattern(pattern, obj[key]);
422
484
  }
485
+ // ── :replace:FROM:TO ── @since 3.6.2
486
+ // Literal substring replacement on a string value. `to` defaults to "" (deletion).
487
+ // Limitation: `from` and `to` cannot themselves contain `:` since the marker
488
+ // chain is colon-delimited; for those cases use {interpolation} instead.
489
+ if (markers.includes('replace') && typeof obj[key] === 'string') {
490
+ const idx = markers.indexOf('replace');
491
+ const from = markers[idx + 1];
492
+ const to = markers[idx + 2] ?? '';
493
+ if (from !== undefined && from !== '') {
494
+ obj[key] = obj[key].split(from).join(to);
495
+ }
496
+ }
497
+ // ── :sort / :sort:desc ── @since 3.6.2
498
+ // Sort an array. Default direction is ascending; `:sort:desc` reverses.
499
+ // Numbers compare numerically; mixed/string entries fall back to localeCompare.
500
+ if (markers.includes('sort') && Array.isArray(obj[key])) {
501
+ const idx = markers.indexOf('sort');
502
+ const dir = markers[idx + 1];
503
+ const sorted = obj[key].slice().sort((a, b) => {
504
+ if (typeof a === 'number' && typeof b === 'number')
505
+ return a - b;
506
+ return String(a).localeCompare(String(b));
507
+ });
508
+ if (dir === 'desc')
509
+ sorted.reverse();
510
+ obj[key] = sorted;
511
+ }
512
+ // ── :sum ── @since 3.6.2
513
+ // Sum the numeric items of an array. Non-numeric entries are ignored.
514
+ // Returns an integer when all summands are integers, otherwise a float.
515
+ if (markers.includes('sum') && Array.isArray(obj[key])) {
516
+ let total = 0;
517
+ let allInt = true;
518
+ for (const v of obj[key]) {
519
+ if (typeof v === 'number') {
520
+ total += v;
521
+ if (!Number.isInteger(v))
522
+ allInt = false;
523
+ }
524
+ else if (typeof v === 'string' && v !== '' && !isNaN(Number(v))) {
525
+ const n = Number(v);
526
+ total += n;
527
+ if (!Number.isInteger(n))
528
+ allInt = false;
529
+ }
530
+ }
531
+ obj[key] = allInt ? Math.trunc(total) : total;
532
+ }
423
533
  // ── :fallback ──
424
534
  // Syntax: key:fallback:DEFAULT_PATH value
425
535
  // If value is empty OR file does not exist, use the fallback.
@@ -511,7 +621,7 @@ function resolve(obj, options = {}, root, includesMap, _resolveDepth = 0, _curre
511
621
  }
512
622
  }
513
623
  catch (e) {
514
- obj[key] = `WATCH_ERR: ${e.message}`;
624
+ obj[key] = `WATCH_ERR: ${fmtIoErr(e, filePath)}`;
515
625
  }
516
626
  }
517
627
  }
@@ -555,8 +665,11 @@ function applyInheritance(obj) {
555
665
  const meta = metaMap[key];
556
666
  if (!meta || !meta.markers.includes('inherit'))
557
667
  continue;
668
+ // Two supported syntaxes:
669
+ // production:inherit:base → "base" is markers[idx+1]
670
+ // production:inherit base → "base" is meta.args[0] (parser promotes value into args)
558
671
  const idx = meta.markers.indexOf('inherit');
559
- const parentName = meta.markers[idx + 1];
672
+ const parentName = meta.markers[idx + 1] || (meta.args && meta.args[0]);
560
673
  if (!parentName)
561
674
  continue;
562
675
  const parentObj = obj[parentName];
@@ -817,7 +930,9 @@ function extractFromFileContent(content, keyPath, ext) {
817
930
  }
818
931
  }
819
932
  // ─── Helpers ──────────────────────────────────────────────
820
- /** Serialize a value to SYNX format string (for :prompt marker). */
933
+ /** Serialize a value to SYNX format string (for :prompt marker).
934
+ * Keys are emitted in sorted order so that LLM prompts are deterministic
935
+ * across runs and across language bindings (matches the Rust engine). */
821
936
  function stringifyValue(value, indent) {
822
937
  const sp = ' '.repeat(indent);
823
938
  if (value === null || value === undefined)
@@ -834,9 +949,10 @@ function stringifyValue(value, indent) {
834
949
  }
835
950
  if (typeof value === 'object') {
836
951
  let out = '';
837
- for (const [k, v] of Object.entries(value)) {
838
- if (k === '__synx')
839
- continue;
952
+ const obj = value;
953
+ const keys = Object.keys(obj).filter(k => k !== '__synx').sort();
954
+ for (const k of keys) {
955
+ const v = obj[k];
840
956
  if (v && typeof v === 'object' && !Array.isArray(v)) {
841
957
  out += `${sp}${k}\n`;
842
958
  out += stringifyValue(v, indent + 2);
@@ -867,6 +983,56 @@ function castPrimitive(val) {
867
983
  return parseFloat(val);
868
984
  return val;
869
985
  }
986
+ /**
987
+ * CLDR-inspired plural category selection.
988
+ * Mirrors the Rust `plural_category` table in `synx-core::engine`.
989
+ */
990
+ function pluralCategory(lang, n) {
991
+ const absN = Math.abs(n);
992
+ const n10 = absN % 10;
993
+ const n100 = absN % 100;
994
+ switch (lang) {
995
+ case 'ru':
996
+ case 'uk':
997
+ case 'be':
998
+ case 'pl':
999
+ if (n10 === 1 && n100 !== 11)
1000
+ return 'one';
1001
+ if (n10 >= 2 && n10 <= 4 && !(n100 >= 12 && n100 <= 14))
1002
+ return 'few';
1003
+ return 'many';
1004
+ case 'cs':
1005
+ case 'sk':
1006
+ if (absN === 1)
1007
+ return 'one';
1008
+ if (absN >= 2 && absN <= 4)
1009
+ return 'few';
1010
+ return 'other';
1011
+ case 'ar':
1012
+ if (absN === 0)
1013
+ return 'zero';
1014
+ if (absN === 1)
1015
+ return 'one';
1016
+ if (absN === 2)
1017
+ return 'two';
1018
+ if (n100 >= 3 && n100 <= 10)
1019
+ return 'few';
1020
+ if (n100 >= 11 && n100 <= 99)
1021
+ return 'many';
1022
+ return 'other';
1023
+ case 'fr':
1024
+ case 'pt':
1025
+ return absN <= 1 ? 'one' : 'other';
1026
+ case 'ja':
1027
+ case 'zh':
1028
+ case 'ko':
1029
+ case 'vi':
1030
+ case 'th':
1031
+ return 'other';
1032
+ default:
1033
+ return absN === 1 ? 'one' : 'other';
1034
+ }
1035
+ }
870
1036
  const DELIM_MAP = {
871
1037
  space: ' ', pipe: '|', dash: '-', dot: '.', semi: ';', tab: '\t', slash: '/',
872
1038
  };
@@ -935,6 +1101,50 @@ function replaceVars(expr, vars) {
935
1101
  result = replaceWord(result, k, v);
936
1102
  return result;
937
1103
  }
1104
+ /**
1105
+ * Substitute dot-path references (`a.b.c`) in a calc expression with their
1106
+ * numeric values from the root tree. Tokens without a dot are left unchanged.
1107
+ * Matches the Rust `dot_resolved` pass in `synx-core::engine`.
1108
+ */
1109
+ function replaceDotPaths(expr, root) {
1110
+ let out = '';
1111
+ let i = 0;
1112
+ const len = expr.length;
1113
+ while (i < len) {
1114
+ const code = expr.charCodeAt(i);
1115
+ if (isWordChar(code)) {
1116
+ const start = i;
1117
+ let hasDot = false;
1118
+ while (i < len) {
1119
+ const c = expr.charCodeAt(i);
1120
+ if (isWordChar(c)) {
1121
+ i++;
1122
+ }
1123
+ else if (c === 46) { // '.'
1124
+ hasDot = true;
1125
+ i++;
1126
+ }
1127
+ else {
1128
+ break;
1129
+ }
1130
+ }
1131
+ const token = expr.substring(start, i);
1132
+ if (hasDot && token.indexOf('.') !== -1) {
1133
+ const val = deepGet(root, token);
1134
+ if (typeof val === 'number') {
1135
+ out += String(val);
1136
+ continue;
1137
+ }
1138
+ }
1139
+ out += token;
1140
+ }
1141
+ else {
1142
+ out += expr[i];
1143
+ i++;
1144
+ }
1145
+ }
1146
+ return out;
1147
+ }
938
1148
  function allowSpamAccess(bucketKey, maxCalls, windowSec) {
939
1149
  const now = Date.now();
940
1150
  const windowMs = windowSec * 1000;