@socketsecurity/lib 5.20.1 → 5.21.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.
@@ -21,9 +21,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var parse_exports = {};
22
22
  __export(parse_exports, {
23
23
  isJsonPrimitive: () => isJsonPrimitive,
24
- jsonParse: () => jsonParse
24
+ jsonParse: () => jsonParse,
25
+ safeJsonParse: () => safeJsonParse
25
26
  });
26
27
  module.exports = __toCommonJS(parse_exports);
28
+ var import_validate = require("../schema/validate");
27
29
  var import_strings = require("../strings");
28
30
  const JSONParse = JSON.parse;
29
31
  // @__NO_SIDE_EFFECTS__
@@ -69,8 +71,44 @@ function jsonParse(content, options) {
69
71
  }
70
72
  return void 0;
71
73
  }
74
+ const DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
75
+ function prototypePollutionReviver(key, value) {
76
+ if (DANGEROUS_KEYS.has(key)) {
77
+ throw new Error(
78
+ "JSON contains potentially malicious prototype pollution keys"
79
+ );
80
+ }
81
+ return value;
82
+ }
83
+ const DEFAULT_MAX_SIZE = 10 * 1024 * 1024;
84
+ // @__NO_SIDE_EFFECTS__
85
+ function safeJsonParse(jsonString, schema, options = {}) {
86
+ const { allowPrototype = false, maxSize = DEFAULT_MAX_SIZE } = options;
87
+ const byteLength = Buffer.byteLength(jsonString, "utf8");
88
+ if (byteLength > maxSize) {
89
+ throw new Error(
90
+ `JSON string exceeds maximum size limit${maxSize !== DEFAULT_MAX_SIZE ? ` of ${maxSize} bytes` : ""}`
91
+ );
92
+ }
93
+ let parsed;
94
+ try {
95
+ parsed = allowPrototype ? JSONParse(jsonString) : JSONParse(jsonString, prototypePollutionReviver);
96
+ } catch (error) {
97
+ throw new Error(`Failed to parse JSON: ${error}`);
98
+ }
99
+ if (schema) {
100
+ const result = (0, import_validate.validateSchema)(schema, parsed);
101
+ if (!result.ok) {
102
+ const summary = result.errors.map((e) => `${e.path.join(".") || "(root)"}: ${e.message}`).join(", ");
103
+ throw new Error(`Validation failed: ${summary}`);
104
+ }
105
+ return result.value;
106
+ }
107
+ return parsed;
108
+ }
72
109
  // Annotate the CommonJS export names for ESM import in node:
73
110
  0 && (module.exports = {
74
111
  isJsonPrimitive,
75
- jsonParse
112
+ jsonParse,
113
+ safeJsonParse
76
114
  });
@@ -72,6 +72,55 @@ export type JsonValue = JsonPrimitive | JsonObject | JsonArray;
72
72
  * ```
73
73
  */
74
74
  export type JsonReviver = (key: string, value: unknown) => unknown;
75
+ /**
76
+ * Options for `safeJsonParse`: security controls for untrusted JSON.
77
+ *
78
+ * Distinct from `JsonParseOptions` (which is scoped to reviver /
79
+ * error-handling for trusted-source fs reads). Use this type when
80
+ * parsing user input, network payloads, or anything beyond a trust
81
+ * boundary.
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * const options: SafeJsonParseOptions = {
86
+ * maxSize: 1024 * 1024, // 1MB limit
87
+ * allowPrototype: false // Block prototype pollution
88
+ * }
89
+ * ```
90
+ */
91
+ export interface SafeJsonParseOptions {
92
+ /**
93
+ * Allow dangerous prototype pollution keys (`__proto__`, `constructor`, `prototype`).
94
+ * Set to `true` only if you trust the JSON source completely.
95
+ *
96
+ * @default false
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * // Will throw error by default
101
+ * safeJsonParse('{"__proto__": {"polluted": true}}')
102
+ *
103
+ * // Allows the parse (dangerous!)
104
+ * safeJsonParse('{"__proto__": {"polluted": true}}', undefined, {
105
+ * allowPrototype: true
106
+ * })
107
+ * ```
108
+ */
109
+ allowPrototype?: boolean | undefined;
110
+ /**
111
+ * Maximum allowed size of JSON string in bytes.
112
+ * Prevents memory exhaustion from extremely large payloads.
113
+ *
114
+ * @default 10_485_760 (10 MB)
115
+ *
116
+ * @example
117
+ * ```ts
118
+ * // Limit to 1KB
119
+ * safeJsonParse(jsonString, undefined, { maxSize: 1024 })
120
+ * ```
121
+ */
122
+ maxSize?: number | undefined;
123
+ }
75
124
  /**
76
125
  * Options for JSON parsing operations.
77
126
  */
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Options for memoization behavior.
7
7
  */
8
- export type MemoizeOptions<Args extends unknown[], _Result = unknown> = {
8
+ export type MemoizeOptions<Args extends unknown[]> = {
9
9
  /** Custom cache key generator (defaults to JSON.stringify) */
10
10
  keyGen?: (...args: Args) => string;
11
11
  /** Maximum cache size (LRU eviction when exceeded) */
@@ -49,7 +49,7 @@ export declare function clearAllMemoizationCaches(): void;
49
49
  * }
50
50
  * }
51
51
  */
52
- export declare function Memoize(options?: MemoizeOptions<unknown[], unknown>): (_target: unknown, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
52
+ export declare function Memoize(options?: MemoizeOptions<unknown[]>): (_target: unknown, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
53
53
  /**
54
54
  * Memoize a function with configurable caching behavior.
55
55
  * Caches function results to avoid repeated computation.
@@ -69,7 +69,7 @@ export declare function Memoize(options?: MemoizeOptions<unknown[], unknown>): (
69
69
  * expensiveOperation(1000) // Computed
70
70
  * expensiveOperation(1000) // Cached
71
71
  */
72
- export declare function memoize<Args extends unknown[], Result>(fn: (...args: Args) => Result, options?: MemoizeOptions<Args, Result>): (...args: Args) => Result;
72
+ export declare function memoize<Args extends unknown[], Result>(fn: (...args: Args) => Result, options?: MemoizeOptions<Args>): (...args: Args) => Result;
73
73
  /**
74
74
  * Memoize an async function.
75
75
  * Similar to memoize() but handles promises properly.
@@ -89,26 +89,7 @@ export declare function memoize<Args extends unknown[], Result>(fn: (...args: Ar
89
89
  * await fetchUser('123') // Fetches from API
90
90
  * await fetchUser('123') // Returns cached result
91
91
  */
92
- export declare function memoizeAsync<Args extends unknown[], Result>(fn: (...args: Args) => Promise<Result>, options?: MemoizeOptions<Args, Result>): (...args: Args) => Promise<Result>;
93
- /**
94
- * Create a debounced memoized function.
95
- * Combines memoization with debouncing for expensive operations.
96
- *
97
- * @param fn - Function to memoize and debounce
98
- * @param wait - Debounce wait time in milliseconds
99
- * @param options - Memoization options
100
- * @returns Debounced memoized function
101
- *
102
- * @example
103
- * import { memoizeDebounced } from '@socketsecurity/lib/memoization'
104
- *
105
- * const search = memoizeDebounced(
106
- * (query: string) => performSearch(query),
107
- * 300,
108
- * { name: 'search' }
109
- * )
110
- */
111
- export declare function memoizeDebounced<Args extends unknown[], Result>(fn: (...args: Args) => Result, wait: number, options?: MemoizeOptions<Args, Result>): (...args: Args) => Result;
92
+ export declare function memoizeAsync<Args extends unknown[], Result>(fn: (...args: Args) => Promise<Result>, options?: MemoizeOptions<Args>): (...args: Args) => Promise<Result>;
112
93
  /**
113
94
  * Memoize with WeakMap for object keys.
114
95
  * Allows garbage collection when objects are no longer referenced.
@@ -24,7 +24,6 @@ __export(memoization_exports, {
24
24
  clearAllMemoizationCaches: () => clearAllMemoizationCaches,
25
25
  memoize: () => memoize,
26
26
  memoizeAsync: () => memoizeAsync,
27
- memoizeDebounced: () => memoizeDebounced,
28
27
  memoizeWeak: () => memoizeWeak,
29
28
  once: () => once
30
29
  });
@@ -78,15 +77,13 @@ function memoize(fn, options = {}) {
78
77
  throw new TypeError("TTL must be non-negative");
79
78
  }
80
79
  const cache = /* @__PURE__ */ new Map();
81
- const accessOrder = [];
82
80
  cacheRegistry.push(() => {
83
81
  cache.clear();
84
- accessOrder.length = 0;
85
82
  });
86
83
  function evictLRU() {
87
- if (cache.size >= maxSize && accessOrder.length > 0) {
88
- const oldest = accessOrder.shift();
89
- if (oldest) {
84
+ if (cache.size >= maxSize) {
85
+ const oldest = cache.keys().next().value;
86
+ if (oldest !== void 0) {
90
87
  cache.delete(oldest);
91
88
  (0, import_debug.debugLog)(`[memoize:${name}] clear`, {
92
89
  key: oldest,
@@ -107,19 +104,12 @@ function memoize(fn, options = {}) {
107
104
  if (cached) {
108
105
  if (!isExpired(cached)) {
109
106
  cached.hits++;
110
- const index2 = accessOrder.indexOf(key);
111
- if (index2 !== -1) {
112
- accessOrder.splice(index2, 1);
113
- }
114
- accessOrder.push(key);
107
+ cache.delete(key);
108
+ cache.set(key, cached);
115
109
  (0, import_debug.debugLog)(`[memoize:${name}] hit`, { key, hits: cached.hits });
116
110
  return cached.value;
117
111
  }
118
112
  cache.delete(key);
119
- const index = accessOrder.indexOf(key);
120
- if (index !== -1) {
121
- accessOrder.splice(index, 1);
122
- }
123
113
  }
124
114
  (0, import_debug.debugLog)(`[memoize:${name}] miss`, { key });
125
115
  const value = fn(...args);
@@ -129,7 +119,6 @@ function memoize(fn, options = {}) {
129
119
  timestamp: Date.now(),
130
120
  hits: 0
131
121
  });
132
- accessOrder.push(key);
133
122
  (0, import_debug.debugLog)(`[memoize:${name}] set`, { key, cacheSize: cache.size });
134
123
  return value;
135
124
  };
@@ -142,15 +131,13 @@ function memoizeAsync(fn, options = {}) {
142
131
  ttl = Number.POSITIVE_INFINITY
143
132
  } = options;
144
133
  const cache = /* @__PURE__ */ new Map();
145
- const accessOrder = [];
146
134
  cacheRegistry.push(() => {
147
135
  cache.clear();
148
- accessOrder.length = 0;
149
136
  });
150
137
  function evictLRU() {
151
- if (cache.size >= maxSize && accessOrder.length > 0) {
152
- const oldest = accessOrder.shift();
153
- if (oldest) {
138
+ if (cache.size >= maxSize) {
139
+ const oldest = cache.keys().next().value;
140
+ if (oldest !== void 0) {
154
141
  cache.delete(oldest);
155
142
  (0, import_debug.debugLog)(`[memoizeAsync:${name}] clear`, {
156
143
  key: oldest,
@@ -165,6 +152,10 @@ function memoizeAsync(fn, options = {}) {
165
152
  }
166
153
  return Date.now() - entry.timestamp > ttl;
167
154
  }
155
+ function bumpRecency(key, entry) {
156
+ cache.delete(key);
157
+ cache.set(key, entry);
158
+ }
168
159
  const refreshing = /* @__PURE__ */ new Map();
169
160
  return async function memoized(...args) {
170
161
  const key = keyGen(...args);
@@ -172,29 +163,17 @@ function memoizeAsync(fn, options = {}) {
172
163
  if (cached) {
173
164
  if (!isExpired(cached)) {
174
165
  cached.hits++;
175
- const index2 = accessOrder.indexOf(key);
176
- if (index2 !== -1) {
177
- accessOrder.splice(index2, 1);
178
- }
179
- accessOrder.push(key);
166
+ bumpRecency(key, cached);
180
167
  (0, import_debug.debugLog)(`[memoizeAsync:${name}] hit`, { key, hits: cached.hits });
181
168
  return await cached.value;
182
169
  }
183
170
  const inflight = refreshing.get(key);
184
171
  if (inflight) {
185
172
  (0, import_debug.debugLog)(`[memoizeAsync:${name}] stale-dedup`, { key });
186
- const inflightIndex = accessOrder.indexOf(key);
187
- if (inflightIndex !== -1) {
188
- accessOrder.splice(inflightIndex, 1);
189
- }
190
- accessOrder.push(key);
173
+ bumpRecency(key, cached);
191
174
  return await inflight;
192
175
  }
193
176
  cache.delete(key);
194
- const index = accessOrder.indexOf(key);
195
- if (index !== -1) {
196
- accessOrder.splice(index, 1);
197
- }
198
177
  }
199
178
  (0, import_debug.debugLog)(`[memoizeAsync:${name}] miss`, { key });
200
179
  const promise = fn(...args).then(
@@ -210,10 +189,6 @@ function memoizeAsync(fn, options = {}) {
210
189
  (error) => {
211
190
  refreshing.delete(key);
212
191
  cache.delete(key);
213
- const index = accessOrder.indexOf(key);
214
- if (index !== -1) {
215
- accessOrder.splice(index, 1);
216
- }
217
192
  (0, import_debug.debugLog)(`[memoizeAsync:${name}] error`, { key, error });
218
193
  throw error;
219
194
  }
@@ -225,24 +200,10 @@ function memoizeAsync(fn, options = {}) {
225
200
  timestamp: Date.now(),
226
201
  hits: 0
227
202
  });
228
- accessOrder.push(key);
229
203
  (0, import_debug.debugLog)(`[memoizeAsync:${name}] set`, { key, cacheSize: cache.size });
230
204
  return await promise;
231
205
  };
232
206
  }
233
- function memoizeDebounced(fn, wait, options = {}) {
234
- const memoized = memoize(fn, options);
235
- let timeoutId;
236
- return function debounced(...args) {
237
- if (timeoutId) {
238
- clearTimeout(timeoutId);
239
- }
240
- timeoutId = setTimeout(() => {
241
- memoized(...args);
242
- }, wait);
243
- return memoized(...args);
244
- };
245
- }
246
207
  function memoizeWeak(fn) {
247
208
  const cache = /* @__PURE__ */ new WeakMap();
248
209
  return function memoized(key) {
@@ -276,7 +237,6 @@ function once(fn) {
276
237
  clearAllMemoizationCaches,
277
238
  memoize,
278
239
  memoizeAsync,
279
- memoizeDebounced,
280
240
  memoizeWeak,
281
241
  once
282
242
  });
@@ -42,9 +42,16 @@ var import_objects = require("../objects");
42
42
  var import_strings = require("../strings");
43
43
  // @__NO_SIDE_EFFECTS__
44
44
  function getRepoUrlDetails(repoUrl = "") {
45
- const userAndRepo = repoUrl.replace(/^.+github.com\//, "").split("/");
45
+ const match = /^(?:[a-z][a-z+]*:\/\/)(?:[^/@]+@)?github\.com\/([^?#]+)(?:[?#]|$)/i.exec(
46
+ repoUrl
47
+ );
48
+ if (!match || !match[1]) {
49
+ return { user: "", project: "" };
50
+ }
51
+ const userAndRepo = match[1].split("/");
46
52
  const user = userAndRepo[0] || "";
47
- const project = userAndRepo.length > 1 ? (userAndRepo[1]?.endsWith(".git") ? userAndRepo[1].slice(0, -4) : userAndRepo[1]) || "" : "";
53
+ const rawProject = userAndRepo[1] ?? "";
54
+ const project = rawProject.endsWith(".git") ? rawProject.slice(0, -4) : rawProject;
48
55
  return { user, project };
49
56
  }
50
57
  // @__NO_SIDE_EFFECTS__
@@ -136,9 +136,7 @@ class ProcessLockManager {
136
136
  if (!stats) {
137
137
  return false;
138
138
  }
139
- const ageSeconds = Math.floor((Date.now() - stats.mtime.getTime()) / 1e3);
140
- const staleSeconds = Math.floor(staleMs / 1e3);
141
- return ageSeconds > staleSeconds;
139
+ return Date.now() - stats.mtime.getTime() > staleMs;
142
140
  } catch {
143
141
  return false;
144
142
  }
@@ -186,9 +184,6 @@ class ProcessLockManager {
186
184
  }
187
185
  }
188
186
  const fs = /* @__PURE__ */ getFs();
189
- if (fs.existsSync(lockPath)) {
190
- throw new Error(`Lock already exists: ${lockPath}`);
191
- }
192
187
  const parent = (/* @__PURE__ */ getPath()).dirname(lockPath);
193
188
  if (parent && parent !== "." && parent !== lockPath) {
194
189
  fs.mkdirSync(parent, { recursive: true });
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * @fileoverview Bounded concurrency promise queue.
3
3
  * Exports the `PromiseQueue` class, which limits how many async tasks run
4
- * simultaneously, supports an optional max queue length (dropping the oldest
5
- * pending task when exceeded), and exposes an idle-wait helper.
4
+ * simultaneously, supports an optional max queue length (new tasks beyond
5
+ * the cap are rejected with "Task dropped: queue length exceeded"), and
6
+ * exposes an idle-wait helper.
6
7
  */
7
8
  export declare class PromiseQueue {
8
9
  private queue;
@@ -13,13 +14,17 @@ export declare class PromiseQueue {
13
14
  /**
14
15
  * Creates a new PromiseQueue
15
16
  * @param maxConcurrency - Maximum number of promises that can run concurrently
16
- * @param maxQueueLength - Maximum queue size (older tasks are dropped if exceeded)
17
+ * @param maxQueueLength - Maximum queue size; submissions past the cap
18
+ * reject with "Task dropped: queue length exceeded" instead of evicting
19
+ * a caller that has been waiting patiently. Callers must handle this
20
+ * rejection or they'll see an unhandled rejection.
17
21
  */
18
22
  constructor(maxConcurrency: number, maxQueueLength?: number | undefined);
19
23
  /**
20
24
  * Add a task to the queue
21
25
  * @param fn - Async function to execute
22
- * @returns Promise that resolves with the function's result
26
+ * @returns Promise that resolves with the function's result, or rejects
27
+ * with "Task dropped: queue length exceeded" if the queue is full.
23
28
  */
24
29
  add<T>(fn: () => Promise<T>): Promise<T>;
25
30
  private runNext;
@@ -32,7 +32,10 @@ class PromiseQueue {
32
32
  /**
33
33
  * Creates a new PromiseQueue
34
34
  * @param maxConcurrency - Maximum number of promises that can run concurrently
35
- * @param maxQueueLength - Maximum queue size (older tasks are dropped if exceeded)
35
+ * @param maxQueueLength - Maximum queue size; submissions past the cap
36
+ * reject with "Task dropped: queue length exceeded" instead of evicting
37
+ * a caller that has been waiting patiently. Callers must handle this
38
+ * rejection or they'll see an unhandled rejection.
36
39
  */
37
40
  constructor(maxConcurrency, maxQueueLength) {
38
41
  this.maxConcurrency = maxConcurrency;
@@ -44,17 +47,16 @@ class PromiseQueue {
44
47
  /**
45
48
  * Add a task to the queue
46
49
  * @param fn - Async function to execute
47
- * @returns Promise that resolves with the function's result
50
+ * @returns Promise that resolves with the function's result, or rejects
51
+ * with "Task dropped: queue length exceeded" if the queue is full.
48
52
  */
49
53
  async add(fn) {
50
54
  return await new Promise((resolve, reject) => {
51
- const task = { fn, resolve, reject };
52
55
  if (this.maxQueueLength !== void 0 && this.queue.length >= this.maxQueueLength) {
53
- const droppedTask = this.queue.shift();
54
- if (droppedTask) {
55
- droppedTask.reject(new Error("Task dropped: queue length exceeded"));
56
- }
56
+ reject(new Error("Task dropped: queue length exceeded"));
57
+ return;
57
58
  }
59
+ const task = { fn, resolve, reject };
58
60
  this.queue.push(task);
59
61
  this.runNext();
60
62
  });
@@ -440,3 +440,44 @@ export declare function pRetry<T>(callbackFn: (...args: unknown[]) => Promise<T>
440
440
  * // => { retries: 5, minTimeout: 200, maxTimeout: 5000, factor: 2 }
441
441
  */
442
442
  export declare function resolveRetryOptions(options?: number | RetryOptions | undefined): RetryOptions;
443
+ /**
444
+ * Shape returned by {@link withResolvers}: a fresh pending promise plus
445
+ * the `resolve` / `reject` handles that settle it.
446
+ *
447
+ * Matches the spec return-shape exactly
448
+ * ([ECMA-262 §27.2.4.9](https://tc39.es/ecma262/#sec-promise.withResolvers)).
449
+ */
450
+ export interface PromiseWithResolvers<T> {
451
+ /** The pending promise. */
452
+ promise: Promise<T>;
453
+ /** Resolves {@link promise} with the given value (or thenable). */
454
+ resolve: (value: T | PromiseLike<T>) => void;
455
+ /** Rejects {@link promise} with the given reason. */
456
+ reject: (reason?: unknown) => void;
457
+ }
458
+ /**
459
+ * Create a pending promise together with its `resolve` and `reject`
460
+ * handles as first-class values, per
461
+ * [ECMA-262 §27.2.4.9](https://tc39.es/ecma262/#sec-promise.withResolvers).
462
+ *
463
+ * Bound to native `Promise.withResolvers` when available (Node 20.12+ /
464
+ * 21+ / 22+; V8 ≥ 12.0); otherwise falls back to a spec-equivalent
465
+ * `new Promise(executor)` implementation that captures the handles via
466
+ * closure. The returned object always has own data properties `promise`,
467
+ * `resolve`, `reject` on `Object.prototype` — writable, enumerable, and
468
+ * configurable — matching the spec's `CreateDataPropertyOrThrow` steps.
469
+ *
470
+ * Use this instead of the manual
471
+ * `let resolve; const p = new Promise(r => { resolve = r })` dance for
472
+ * deferred-resolution patterns (event-driven bridges, adapter layers,
473
+ * handshake signaling) where the settle path lives outside the executor.
474
+ *
475
+ * @example
476
+ * ```typescript
477
+ * const { promise, resolve, reject } = withResolvers<string>()
478
+ * emitter.once('ready', () => resolve('ok'))
479
+ * emitter.once('error', err => reject(err))
480
+ * const result = await promise
481
+ * ```
482
+ */
483
+ export declare const withResolvers: <T>() => PromiseWithResolvers<T>;
package/dist/promises.js CHANGED
@@ -27,7 +27,8 @@ __export(promises_exports, {
27
27
  pFilter: () => pFilter,
28
28
  pFilterChunk: () => pFilterChunk,
29
29
  pRetry: () => pRetry,
30
- resolveRetryOptions: () => resolveRetryOptions
30
+ resolveRetryOptions: () => resolveRetryOptions,
31
+ withResolvers: () => withResolvers
31
32
  });
32
33
  module.exports = __toCommonJS(promises_exports);
33
34
  var import_arrays = require("./arrays");
@@ -258,6 +259,21 @@ function resolveRetryOptions(options) {
258
259
  }
259
260
  return options ? { ...defaults, ...options } : defaults;
260
261
  }
262
+ const maybeNativeWithResolvers = Promise.withResolvers;
263
+ const withResolvers = typeof maybeNativeWithResolvers === "function" ? (
264
+ // Bind so callers who destructure the export don't lose `this`.
265
+ maybeNativeWithResolvers.bind(
266
+ Promise
267
+ )
268
+ ) : () => {
269
+ let resolve;
270
+ let reject;
271
+ const promise = new Promise((res, rej) => {
272
+ resolve = res;
273
+ reject = rej;
274
+ });
275
+ return { promise, resolve, reject };
276
+ };
261
277
  // Annotate the CommonJS export names for ESM import in node:
262
278
  0 && (module.exports = {
263
279
  normalizeIterationOptions,
@@ -267,5 +283,6 @@ function resolveRetryOptions(options) {
267
283
  pFilter,
268
284
  pFilterChunk,
269
285
  pRetry,
270
- resolveRetryOptions
286
+ resolveRetryOptions,
287
+ withResolvers
271
288
  });
package/dist/regexps.d.ts CHANGED
@@ -1,15 +1,6 @@
1
1
  /**
2
- * @fileoverview Regular expression utilities including escape-string-regexp implementation.
3
- * Provides regex escaping and pattern matching helpers.
2
+ * @fileoverview Regular expression utilities including a spec-compliant
3
+ * `RegExp.escape` fallback. Provides regex escaping and pattern matching
4
+ * helpers.
4
5
  */
5
- /**
6
- * Escape special characters in a string for use in a regular expression.
7
- *
8
- * @example
9
- * ```typescript
10
- * escapeRegExp('foo.bar') // 'foo\\.bar'
11
- * escapeRegExp('a+b*c?') // 'a\\+b\\*c\\?'
12
- * new RegExp(escapeRegExp('[test]')) // /\[test\]/
13
- * ```
14
- */
15
- export declare function escapeRegExp(str: string): string;
6
+ export declare const escapeRegExp: (str: string) => string;
package/dist/regexps.js CHANGED
@@ -23,10 +23,67 @@ __export(regexps_exports, {
23
23
  escapeRegExp: () => escapeRegExp
24
24
  });
25
25
  module.exports = __toCommonJS(regexps_exports);
26
- // @__NO_SIDE_EFFECTS__
27
- function escapeRegExp(str) {
28
- return str.replace(/[\\|{}()[\]^$+*?.]/g, "\\$&");
26
+ const SYNTAX_CHARACTERS = new Set("^$\\.*+?()[]{}|/");
27
+ const CONTROL_ESCAPES = /* @__PURE__ */ new Map([
28
+ [9, "\\t"],
29
+ [10, "\\n"],
30
+ [11, "\\v"],
31
+ [12, "\\f"],
32
+ [13, "\\r"]
33
+ ]);
34
+ const OTHER_PUNCTUATORS = new Set(",-=<>#&!%:;@~'`\"");
35
+ function isSpecHexEscapeCp(cp) {
36
+ if (OTHER_PUNCTUATORS.has(String.fromCodePoint(cp))) {
37
+ return true;
38
+ }
39
+ if (cp === 10 || cp === 13 || cp === 8232 || cp === 8233) {
40
+ return true;
41
+ }
42
+ if (cp === 9 || cp === 11 || cp === 12 || cp === 32 || cp === 160 || cp === 65279) {
43
+ return true;
44
+ }
45
+ if (cp >= 55296 && cp <= 57343) {
46
+ return true;
47
+ }
48
+ return false;
49
+ }
50
+ function hex2(n) {
51
+ return n.toString(16).padStart(2, "0");
52
+ }
53
+ function hex4(n) {
54
+ return n.toString(16).padStart(4, "0");
55
+ }
56
+ function escapeRegExpFallback(str) {
57
+ let out = "";
58
+ let isFirst = true;
59
+ for (const char of str) {
60
+ const cp = char.codePointAt(0);
61
+ if (isFirst && (cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122)) {
62
+ out += "\\x" + hex2(cp);
63
+ } else if (SYNTAX_CHARACTERS.has(char)) {
64
+ out += "\\" + char;
65
+ } else {
66
+ const ctrl = CONTROL_ESCAPES.get(cp);
67
+ if (ctrl !== void 0) {
68
+ out += ctrl;
69
+ } else if (isSpecHexEscapeCp(cp)) {
70
+ if (cp <= 255) {
71
+ out += "\\x" + hex2(cp);
72
+ } else {
73
+ for (let i = 0; i < char.length; i++) {
74
+ out += "\\u" + hex4(char.charCodeAt(i));
75
+ }
76
+ }
77
+ } else {
78
+ out += char;
79
+ }
80
+ }
81
+ isFirst = false;
82
+ }
83
+ return out;
29
84
  }
85
+ const maybeNativeEscape = RegExp.escape;
86
+ const escapeRegExp = typeof maybeNativeEscape === "function" ? maybeNativeEscape : escapeRegExpFallback;
30
87
  // Annotate the CommonJS export names for ESM import in node:
31
88
  0 && (module.exports = {
32
89
  escapeRegExp
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @fileoverview Throwing twin of `validateSchema`.
3
+ *
4
+ * Use `parseSchema(schema, data)` for fail-fast trust boundaries (app
5
+ * startup, config files, internal assertions). Use the non-throwing
6
+ * `validateSchema` for recoverable input (form fields, API request bodies,
7
+ * anywhere errors need to surface to a user).
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { z } from 'zod'
12
+ * import { parseSchema } from '@socketsecurity/lib/schema/parse'
13
+ *
14
+ * const Config = z.object({ host: z.string(), port: z.number() })
15
+ * const config = parseSchema(Config, json) // throws on invalid
16
+ * ```
17
+ */
18
+ import type { Infer } from './types';
19
+ /**
20
+ * Parse `data` against `schema` and return the validated value.
21
+ *
22
+ * @throws {Error} When validation fails. The message lists all issues as
23
+ * `path: message, path: message, ...`. Use `validateSchema` if you need
24
+ * structured access to the error list.
25
+ */
26
+ export declare function parseSchema<S>(schema: S, data: unknown): Infer<S>;