@ls-stack/utils 3.48.0 → 3.49.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.
@@ -1,6 +1,7 @@
1
1
  import {
2
- deepEqual
3
- } from "./chunk-JQFUKJU5.js";
2
+ exhaustiveCheck
3
+ } from "./chunk-C2SVCIWE.js";
4
+ import "./chunk-JF2MDHOJ.js";
4
5
 
5
6
  // src/partialEqual.ts
6
7
  import { err, ok } from "t-result";
@@ -42,8 +43,24 @@ var match = {
42
43
  partialEqual: (value) => createComparison(["partialEqual", value]),
43
44
  custom: (isEqual) => createComparison(["custom", isEqual]),
44
45
  keyNotBePresent: createComparison(["keyNotBePresent", null]),
45
- any: (...comparisons) => createComparison(["any", comparisons.map((c) => c["~sc"])]),
46
- all: (...comparisons) => createComparison(["all", comparisons.map((c) => c["~sc"])]),
46
+ any: (...values) => createComparison([
47
+ "any",
48
+ values.map((v) => {
49
+ if (isComparison(v)) return v["~sc"];
50
+ if (typeof v === "object" && v !== null)
51
+ return ["partialEqual", v];
52
+ return ["deepEqual", v];
53
+ })
54
+ ]),
55
+ all: (...values) => createComparison([
56
+ "all",
57
+ values.map((v) => {
58
+ if (isComparison(v)) return v["~sc"];
59
+ if (typeof v === "object" && v !== null)
60
+ return ["partialEqual", v];
61
+ return ["deepEqual", v];
62
+ })
63
+ ]),
47
64
  not: {
48
65
  hasType: {
49
66
  string: createComparison(["not", ["hasType", "string"]]),
@@ -74,741 +91,640 @@ var match = {
74
91
  equal: (value) => createComparison(["not", ["deepEqual", value]]),
75
92
  partialEqual: (value) => createComparison(["not", ["partialEqual", value]]),
76
93
  custom: (value) => createComparison(["not", ["custom", value]]),
77
- any: (...comparisons) => createComparison(["not", ["any", comparisons.map((c) => c["~sc"])]]),
78
- all: (...comparisons) => createComparison(["not", ["all", comparisons.map((c) => c["~sc"])]]),
94
+ any: (...values) => createComparison([
95
+ "not",
96
+ [
97
+ "any",
98
+ values.map((v) => {
99
+ if (isComparison(v)) return v["~sc"];
100
+ if (typeof v === "object" && v !== null)
101
+ return ["partialEqual", v];
102
+ return ["deepEqual", v];
103
+ })
104
+ ]
105
+ ]),
106
+ all: (...values) => createComparison([
107
+ "not",
108
+ [
109
+ "all",
110
+ values.map((v) => {
111
+ if (isComparison(v)) return v["~sc"];
112
+ if (typeof v === "object" && v !== null)
113
+ return ["partialEqual", v];
114
+ return ["deepEqual", v];
115
+ })
116
+ ]
117
+ ]),
79
118
  noExtraKeys: (partialShape) => createComparison(["not", ["withNoExtraKeys", partialShape]]),
80
119
  deepNoExtraKeys: (partialShape) => createComparison(["not", ["withDeepNoExtraKeys", partialShape]]),
81
120
  noExtraDefinedKeys: (partialShape) => createComparison(["not", ["noExtraDefinedKeys", partialShape]]),
82
121
  deepNoExtraDefinedKeys: (partialShape) => createComparison(["not", ["deepNoExtraDefinedKeys", partialShape]])
83
122
  }
84
123
  };
85
- function find(iter, tar) {
86
- for (const key of iter.keys()) {
87
- if (partialEqual(key, tar)) return key;
88
- }
89
- }
90
- function executeComparisonWithKeyContext(target, comp, keyExists) {
91
- const [type, value] = comp;
92
- if (type === "keyNotBePresent") {
93
- return !keyExists;
94
- }
95
- if (type === "any") {
96
- for (const childComp of value) {
97
- if (executeComparisonWithKeyContext(target, childComp, keyExists)) {
98
- return true;
99
- }
100
- }
101
- return false;
102
- }
103
- if (type === "not") {
104
- return !executeComparisonWithKeyContext(target, value, keyExists);
105
- }
106
- return executeComparison(target, comp);
124
+ function isComparison(value) {
125
+ return value && typeof value === "object" && "~sc" in value;
107
126
  }
108
- function executeComparison(target, comparison) {
127
+ function executeComparison(target, comparison, context) {
109
128
  const [type, value] = comparison;
110
129
  switch (type) {
111
- case "hasType":
112
- switch (value) {
113
- case "string":
114
- return typeof target === "string";
115
- case "number":
116
- return typeof target === "number";
117
- case "boolean":
118
- return typeof target === "boolean";
119
- case "function":
120
- return typeof target === "function";
121
- case "array":
122
- return Array.isArray(target);
123
- case "object":
124
- return typeof target === "object" && target !== null && !Array.isArray(target);
125
- default:
126
- return false;
127
- }
128
- case "isInstanceOf":
129
- return target instanceof value;
130
130
  case "strStartsWith":
131
- return typeof target === "string" && target.startsWith(value);
132
- case "strEndsWith":
133
- return typeof target === "string" && target.endsWith(value);
134
- case "strContains":
135
- return typeof target === "string" && target.includes(value);
136
- case "strMatchesRegex":
137
- return typeof target === "string" && value.test(target);
138
- case "numIsGreaterThan":
139
- return typeof target === "number" && target > value;
140
- case "numIsGreaterThanOrEqual":
141
- return typeof target === "number" && target >= value;
142
- case "numIsLessThan":
143
- return typeof target === "number" && target < value;
144
- case "numIsLessThanOrEqual":
145
- return typeof target === "number" && target <= value;
146
- case "numIsInRange":
147
- return typeof target === "number" && target >= value[0] && target <= value[1];
148
- case "jsonStringHasPartial":
149
- if (typeof target !== "string") return false;
150
- try {
151
- const parsed = JSON.parse(target);
152
- return partialEqual(parsed, value);
153
- } catch {
154
- return false;
155
- }
156
- case "deepEqual":
157
- return deepEqual(target, value);
158
- case "partialEqual":
159
- return partialEqual(target, value);
160
- case "custom":
161
- return value(target);
162
- case "keyNotBePresent":
163
- return false;
164
- case "any":
165
- for (const comp of value) {
166
- if (executeComparison(target, comp)) {
167
- return true;
168
- }
169
- }
170
- return false;
171
- case "all":
172
- for (const comp of value) {
173
- if (!executeComparison(target, comp)) {
174
- return false;
175
- }
176
- }
177
- return true;
178
- case "not":
179
- return !executeComparison(target, value);
180
- case "withNoExtraKeys":
181
- if (typeof target !== "object" || target === null || Array.isArray(target)) {
131
+ if (typeof target !== "string") {
132
+ addError(context, {
133
+ message: `Expected string starting with "${value}"`,
134
+ received: target
135
+ });
182
136
  return false;
183
137
  }
184
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
138
+ if (!target.startsWith(value)) {
139
+ addError(context, {
140
+ message: `Expected string starting with "${value}"`,
141
+ received: target
142
+ });
185
143
  return false;
186
144
  }
187
- for (const key in target) {
188
- if (has.call(target, key) && !has.call(value, key)) {
189
- return false;
190
- }
191
- }
192
- for (const key in value) {
193
- if (has.call(value, key)) {
194
- if (!has.call(target, key)) {
195
- return false;
196
- }
197
- if (!partialEqual(target[key], value[key])) {
198
- return false;
199
- }
200
- }
201
- }
202
145
  return true;
203
- case "withDeepNoExtraKeys":
204
- if (typeof target !== "object" || target === null || Array.isArray(target)) {
146
+ case "strEndsWith":
147
+ if (typeof target !== "string") {
148
+ addError(context, {
149
+ message: `Expected string ending with "${value}"`,
150
+ received: target
151
+ });
205
152
  return false;
206
153
  }
207
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
154
+ if (!target.endsWith(value)) {
155
+ addError(context, {
156
+ message: `Expected string ending with "${value}"`,
157
+ received: target
158
+ });
208
159
  return false;
209
160
  }
210
- for (const key in target) {
211
- if (has.call(target, key) && !has.call(value, key)) {
212
- return false;
213
- }
214
- }
215
- for (const key in value) {
216
- if (has.call(value, key)) {
217
- if (!has.call(target, key)) {
218
- return false;
219
- }
220
- const targetValue = target[key];
221
- const partialValue = value[key];
222
- if (partialValue && typeof partialValue === "object" && "~sc" in partialValue && partialValue["~sc"][0] === "withDeepNoExtraKeys") {
223
- if (!executeComparison(targetValue, partialValue["~sc"])) {
224
- return false;
225
- }
226
- } else if (partialValue && typeof partialValue === "object" && !Array.isArray(partialValue) && partialValue.constructor === Object && targetValue && typeof targetValue === "object" && !Array.isArray(targetValue) && targetValue.constructor === Object) {
227
- if (!executeComparison(targetValue, [
228
- "withDeepNoExtraKeys",
229
- partialValue
230
- ])) {
231
- return false;
232
- }
233
- } else {
234
- if (!partialEqual(targetValue, partialValue)) {
235
- return false;
236
- }
237
- }
238
- }
239
- }
240
161
  return true;
241
- case "noExtraDefinedKeys":
242
- if (typeof target !== "object" || target === null || Array.isArray(target)) {
162
+ case "strContains":
163
+ if (typeof target !== "string") {
164
+ addError(context, {
165
+ message: `Expected string containing "${value}"`,
166
+ received: target
167
+ });
243
168
  return false;
244
169
  }
245
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
170
+ if (!target.includes(value)) {
171
+ addError(context, {
172
+ message: `Expected string containing "${value}"`,
173
+ received: target
174
+ });
246
175
  return false;
247
176
  }
248
- for (const key in target) {
249
- if (has.call(target, key) && target[key] !== void 0 && !has.call(value, key)) {
250
- return false;
251
- }
252
- }
253
- for (const key in value) {
254
- if (has.call(value, key)) {
255
- if (!has.call(target, key)) {
256
- return false;
257
- }
258
- if (!partialEqual(target[key], value[key])) {
259
- return false;
260
- }
261
- }
262
- }
263
177
  return true;
264
- case "deepNoExtraDefinedKeys":
265
- if (typeof target !== "object" || target === null || Array.isArray(target)) {
178
+ case "strMatchesRegex":
179
+ if (typeof target !== "string") {
180
+ addError(context, {
181
+ message: `Expected string matching regex ${value}`,
182
+ received: target
183
+ });
266
184
  return false;
267
185
  }
268
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
186
+ if (!value.test(target)) {
187
+ addError(context, {
188
+ message: `Expected string matching regex ${value}`,
189
+ received: target
190
+ });
269
191
  return false;
270
192
  }
271
- for (const key in target) {
272
- if (has.call(target, key) && target[key] !== void 0 && !has.call(value, key)) {
273
- return false;
193
+ return true;
194
+ case "hasType": {
195
+ let actualType;
196
+ if (value === "array") {
197
+ actualType = Array.isArray(target) ? "array" : typeof target;
198
+ } else if (value === "object") {
199
+ if (target === null || Array.isArray(target)) {
200
+ actualType = "not-object";
201
+ } else {
202
+ actualType = typeof target;
274
203
  }
204
+ } else {
205
+ actualType = typeof target;
275
206
  }
276
- for (const key in value) {
277
- if (has.call(value, key)) {
278
- if (!has.call(target, key)) {
279
- return false;
280
- }
281
- const targetValue = target[key];
282
- const partialValue = value[key];
283
- if (partialValue && typeof partialValue === "object" && "~sc" in partialValue && partialValue["~sc"][0] === "deepNoExtraDefinedKeys") {
284
- if (!executeComparison(targetValue, partialValue["~sc"])) {
285
- return false;
286
- }
287
- } else if (partialValue && typeof partialValue === "object" && !Array.isArray(partialValue) && partialValue.constructor === Object && targetValue && typeof targetValue === "object" && !Array.isArray(targetValue) && targetValue.constructor === Object) {
288
- if (!executeComparison(targetValue, [
289
- "deepNoExtraDefinedKeys",
290
- partialValue
291
- ])) {
292
- return false;
293
- }
294
- } else {
295
- if (!partialEqual(targetValue, partialValue)) {
296
- return false;
297
- }
298
- }
299
- }
207
+ if (actualType !== value) {
208
+ addError(context, {
209
+ message: `Expected type ${value}`,
210
+ received: target
211
+ });
212
+ return false;
300
213
  }
301
214
  return true;
302
- default:
303
- return false;
304
- }
305
- }
306
- function formatPath(path) {
307
- if (path.length === 0) return "";
308
- let result = path[0] || "";
309
- for (let i = 1; i < path.length; i++) {
310
- const segment = path[i];
311
- if (segment && segment.startsWith("[") && segment.endsWith("]")) {
312
- result += segment;
313
- } else if (segment) {
314
- result += `.${segment}`;
315
215
  }
316
- }
317
- return result;
318
- }
319
- function addError(context, message, received, expected) {
320
- const error = {
321
- path: formatPath(context.path),
322
- message,
323
- received
324
- };
325
- if (expected !== void 0) {
326
- error.expected = expected;
327
- }
328
- context.errors.push(error);
329
- }
330
- function executeComparisonWithErrorCollection(target, comparison, context) {
331
- const [type, value] = comparison;
332
- switch (type) {
333
- case "hasType":
334
- switch (value) {
335
- case "string":
336
- if (typeof target !== "string") {
337
- addError(context, `Expected type string`, target);
338
- }
339
- break;
340
- case "number":
341
- if (typeof target !== "number") {
342
- addError(context, `Expected type number`, target);
343
- }
344
- break;
345
- case "boolean":
346
- if (typeof target !== "boolean") {
347
- addError(context, `Expected type boolean`, target);
348
- }
349
- break;
350
- case "function":
351
- if (typeof target !== "function") {
352
- addError(context, `Expected type function`, target);
353
- }
354
- break;
355
- case "array":
356
- if (!Array.isArray(target)) {
357
- addError(context, `Expected array`, target);
358
- }
359
- break;
360
- case "object":
361
- if (typeof target !== "object" || target === null || Array.isArray(target)) {
362
- addError(context, `Expected object`, target);
363
- }
364
- break;
365
- }
366
- break;
367
- case "isInstanceOf":
368
- if (!(target instanceof value)) {
369
- addError(
370
- context,
371
- `Expected instance of ${value.name}`,
372
- target
373
- );
374
- }
375
- break;
376
- case "strStartsWith":
377
- if (typeof target !== "string" || !target.startsWith(value)) {
378
- addError(
379
- context,
380
- `Expected string starting with "${value}"`,
381
- target
382
- );
383
- }
384
- break;
385
- case "strEndsWith":
386
- if (typeof target !== "string" || !target.endsWith(value)) {
387
- addError(
388
- context,
389
- `Expected string ending with "${value}"`,
390
- target
391
- );
392
- }
393
- break;
394
- case "strContains":
395
- if (typeof target !== "string" || !target.includes(value)) {
396
- addError(
397
- context,
398
- `Expected string containing "${value}"`,
399
- target
400
- );
401
- }
402
- break;
403
- case "strMatchesRegex":
404
- if (typeof target !== "string" || !value.test(target)) {
405
- addError(
406
- context,
407
- `Expected string matching regex ${value}`,
408
- target
409
- );
216
+ case "deepEqual":
217
+ if (!deepEqual(target, value)) {
218
+ addError(context, {
219
+ message: "Values are not deeply equal",
220
+ received: target,
221
+ expected: value
222
+ });
223
+ return false;
410
224
  }
411
- break;
225
+ return true;
412
226
  case "numIsGreaterThan":
413
227
  if (typeof target !== "number" || target <= value) {
414
- addError(
415
- context,
416
- `Expected number greater than ${value}`,
417
- target
418
- );
228
+ addError(context, {
229
+ message: `Expected number greater than ${value}`,
230
+ received: target
231
+ });
232
+ return false;
419
233
  }
420
- break;
234
+ return true;
421
235
  case "numIsGreaterThanOrEqual":
422
236
  if (typeof target !== "number" || target < value) {
423
- addError(
424
- context,
425
- `Expected number greater than or equal to ${value}`,
426
- target
427
- );
237
+ addError(context, {
238
+ message: `Expected number greater than or equal to ${value}`,
239
+ received: target
240
+ });
241
+ return false;
428
242
  }
429
- break;
243
+ return true;
430
244
  case "numIsLessThan":
431
245
  if (typeof target !== "number" || target >= value) {
432
- addError(
433
- context,
434
- `Expected number less than ${value}`,
435
- target
436
- );
246
+ addError(context, {
247
+ message: `Expected number less than ${value}`,
248
+ received: target
249
+ });
250
+ return false;
437
251
  }
438
- break;
252
+ return true;
439
253
  case "numIsLessThanOrEqual":
440
254
  if (typeof target !== "number" || target > value) {
441
- addError(
442
- context,
443
- `Expected number less than or equal to ${value}`,
444
- target
445
- );
255
+ addError(context, {
256
+ message: `Expected number less than or equal to ${value}`,
257
+ received: target
258
+ });
259
+ return false;
446
260
  }
447
- break;
261
+ return true;
448
262
  case "numIsInRange":
449
263
  if (typeof target !== "number" || target < value[0] || target > value[1]) {
450
- addError(
451
- context,
452
- `Expected number in range [${value[0]}, ${value[1]}]`,
453
- target
454
- );
264
+ addError(context, {
265
+ message: `Expected number in range [${value[0]}, ${value[1]}]`,
266
+ received: target
267
+ });
268
+ return false;
455
269
  }
456
- break;
270
+ return true;
457
271
  case "jsonStringHasPartial":
458
272
  if (typeof target !== "string") {
459
- addError(context, `Expected JSON string`, target);
460
- } else {
461
- try {
462
- const parsed = JSON.parse(target);
463
- partialEqualWithErrorCollection(parsed, value, context);
464
- } catch {
465
- addError(
466
- context,
467
- `Expected valid JSON string`,
468
- target
469
- );
470
- }
273
+ addError(context, {
274
+ message: "Expected JSON string",
275
+ received: target
276
+ });
277
+ return false;
471
278
  }
472
- break;
473
- case "deepEqual":
474
- if (!deepEqual(target, value)) {
475
- addError(context, `Expected deep equal`, target, value);
279
+ try {
280
+ const parsed = JSON.parse(target);
281
+ if (!partialEqualInternal(parsed, value, context)) {
282
+ return false;
283
+ }
284
+ } catch {
285
+ addError(context, {
286
+ message: "Expected valid JSON string",
287
+ received: target
288
+ });
289
+ return false;
476
290
  }
477
- break;
291
+ return true;
478
292
  case "partialEqual":
479
- partialEqualWithErrorCollection(target, value, context);
480
- break;
481
- case "custom":
482
- if (!value(target)) {
483
- addError(context, `Custom validation failed`, target, "valid value");
293
+ return partialEqualInternal(target, value, context);
294
+ case "custom": {
295
+ const result = value(target);
296
+ if (result !== true) {
297
+ addError(context, {
298
+ message: `Custom validation failed ${typeof result === "object" ? `: ${result.error}` : ""}`,
299
+ received: target
300
+ });
301
+ return false;
484
302
  }
485
- break;
303
+ return true;
304
+ }
305
+ case "isInstanceOf":
306
+ if (!(target instanceof value)) {
307
+ addError(context, {
308
+ message: `Expected instance of ${value.name}`,
309
+ received: target
310
+ });
311
+ return false;
312
+ }
313
+ return true;
486
314
  case "keyNotBePresent":
487
- addError(context, `Key should not be present`, target);
488
- break;
315
+ addError(context, {
316
+ message: "This property should not be present",
317
+ received: target
318
+ });
319
+ return false;
320
+ case "not": {
321
+ const tempContext = {
322
+ errors: [],
323
+ path: context.path
324
+ };
325
+ const result = executeComparison(target, value, tempContext);
326
+ if (result) {
327
+ addError(context, {
328
+ message: "Expected negated condition to fail",
329
+ received: target,
330
+ expected: { "not match": value }
331
+ });
332
+ return false;
333
+ }
334
+ return true;
335
+ }
489
336
  case "any": {
490
- for (const comp of value) {
491
- const subContext = {
337
+ for (const subComparison of value) {
338
+ const anyTempContext = {
492
339
  errors: [],
493
- path: [...context.path]
340
+ path: context.path
494
341
  };
495
- executeComparisonWithErrorCollection(target, comp, subContext);
496
- if (subContext.errors.length === 0) {
497
- return;
342
+ if (executeComparison(target, subComparison, anyTempContext)) {
343
+ return true;
498
344
  }
499
345
  }
500
- addError(
501
- context,
502
- `None of the alternative comparisons matched`,
503
- target
504
- );
505
- break;
346
+ addError(context, {
347
+ message: "None of the alternative comparisons matched",
348
+ received: target,
349
+ expected: {
350
+ matchAny: value
351
+ }
352
+ });
353
+ return false;
506
354
  }
507
- case "all":
508
- for (const comp of value) {
509
- executeComparisonWithErrorCollection(target, comp, context);
510
- }
511
- break;
512
- case "not": {
513
- const subContext = {
514
- errors: [],
515
- path: [...context.path]
516
- };
517
- executeComparisonWithErrorCollection(target, value, subContext);
518
- if (subContext.errors.length === 0) {
519
- addError(
520
- context,
521
- `Expected negated condition to fail`,
522
- target
523
- );
355
+ case "all": {
356
+ let allMatch = true;
357
+ for (const subComparison of value) {
358
+ if (!executeComparison(target, subComparison, context)) {
359
+ allMatch = false;
360
+ }
524
361
  }
525
- break;
362
+ return allMatch;
526
363
  }
527
364
  case "withNoExtraKeys":
365
+ return checkNoExtraKeys(target, value, context, false);
528
366
  case "withDeepNoExtraKeys":
367
+ return checkNoExtraKeys(target, value, context, true);
529
368
  case "noExtraDefinedKeys":
369
+ return checkNoExtraDefinedKeys(target, value, context, false);
530
370
  case "deepNoExtraDefinedKeys":
531
- addError(
532
- context,
533
- `Complex validation not supported in error collection mode yet`,
534
- target
535
- );
536
- break;
371
+ return checkNoExtraDefinedKeys(target, value, context, true);
372
+ default:
373
+ throw exhaustiveCheck(type);
537
374
  }
538
375
  }
539
- function executeComparisonWithKeyContextAndErrorCollection(target, comp, keyExists, context) {
540
- const [type, value] = comp;
541
- if (type === "keyNotBePresent") {
542
- if (keyExists) {
543
- addError(context, `Key should not be present`, target);
376
+ function formatPath(path) {
377
+ if (path.length === 0) return "";
378
+ let result = path[0] || "";
379
+ for (let i = 1; i < path.length; i++) {
380
+ const segment = path[i];
381
+ if (segment && segment.startsWith("[") && segment.endsWith("]")) {
382
+ result += segment;
383
+ } else if (segment) {
384
+ if (result) {
385
+ result += `.${segment}`;
386
+ } else {
387
+ result += segment;
388
+ }
544
389
  }
545
- return;
546
390
  }
547
- if (type === "any") {
548
- for (const childComp of value) {
549
- const subContext = {
550
- errors: [],
551
- path: [...context.path]
552
- };
553
- executeComparisonWithKeyContextAndErrorCollection(
554
- target,
555
- childComp,
556
- keyExists,
557
- subContext
558
- );
559
- if (subContext.errors.length === 0) {
560
- return;
561
- }
391
+ return result;
392
+ }
393
+ function addError(context, error) {
394
+ context.errors.push({
395
+ path: formatPath(context.path),
396
+ ...error
397
+ });
398
+ }
399
+ function deepEqual(a, b) {
400
+ if (a === b) return true;
401
+ if (Number.isNaN(a) && Number.isNaN(b)) return true;
402
+ if (a === null || b === null) return false;
403
+ if (typeof a !== typeof b) return false;
404
+ if (Array.isArray(a) && Array.isArray(b)) {
405
+ if (a.length !== b.length) return false;
406
+ for (let i = 0; i < a.length; i++) {
407
+ if (!deepEqual(a[i], b[i])) return false;
562
408
  }
563
- addError(
564
- context,
565
- `None of the alternative comparisons matched`,
566
- target,
567
- "any alternative match"
568
- );
569
- return;
409
+ return true;
570
410
  }
571
- if (type === "not") {
572
- const subContext = {
573
- errors: [],
574
- path: [...context.path]
575
- };
576
- executeComparisonWithKeyContextAndErrorCollection(
577
- target,
578
- value,
579
- keyExists,
580
- subContext
581
- );
582
- if (subContext.errors.length === 0) {
583
- addError(
584
- context,
585
- `Expected negated condition to fail`,
586
- target,
587
- "negated condition"
588
- );
411
+ if (typeof a === "object") {
412
+ const keysA = Object.keys(a);
413
+ const keysB = Object.keys(b);
414
+ if (keysA.length !== keysB.length) return false;
415
+ for (const key of keysA) {
416
+ if (!has.call(b, key) || !deepEqual(a[key], b[key])) return false;
589
417
  }
590
- return;
418
+ return true;
591
419
  }
592
- executeComparisonWithErrorCollection(target, comp, context);
420
+ return false;
593
421
  }
594
- function partialEqualWithErrorCollection(target, sub, context) {
595
- if (sub === target) return;
596
- if (sub && typeof sub === "object" && "~sc" in sub) {
597
- executeComparisonWithErrorCollection(target, sub["~sc"], context);
598
- return;
422
+ function partialEqualInternal(target, sub, context) {
423
+ if (isComparison(sub)) {
424
+ return executeComparison(target, sub["~sc"], context);
599
425
  }
600
- if (sub && target && sub.constructor === target.constructor) {
601
- const ctor = sub.constructor;
602
- if (ctor === Date) {
603
- if (sub.getTime() !== target.getTime()) {
604
- addError(context, `Date mismatch`, target, sub);
605
- }
606
- return;
426
+ if (isComparison(sub) && sub["~sc"][0] === "keyNotBePresent") {
427
+ addError(context, {
428
+ message: "Key should not be present",
429
+ received: target,
430
+ expected: "key not present"
431
+ });
432
+ return false;
433
+ }
434
+ if (target === sub) return true;
435
+ if (Number.isNaN(target) && Number.isNaN(sub)) return true;
436
+ if (target === null || sub === null || target === void 0 || sub === void 0) {
437
+ if (target !== sub) {
438
+ addError(context, {
439
+ message: "Value mismatch",
440
+ received: target,
441
+ expected: sub
442
+ });
443
+ return false;
607
444
  }
608
- if (ctor === RegExp) {
609
- if (sub.toString() !== target.toString()) {
610
- addError(context, `RegExp mismatch`, target, sub);
611
- }
612
- return;
445
+ return true;
446
+ }
447
+ if (target instanceof Date && sub instanceof Date) {
448
+ if (target.getTime() !== sub.getTime()) {
449
+ addError(context, {
450
+ message: "Date mismatch",
451
+ received: target,
452
+ expected: sub
453
+ });
454
+ return false;
613
455
  }
614
- if (ctor === Array) {
615
- if (sub.length > target.length) {
616
- addError(
617
- context,
618
- `Array too short: expected at least ${sub.length} elements, got ${target.length}`,
619
- target,
620
- sub
621
- );
622
- return;
623
- }
624
- for (let i = 0; i < sub.length; i++) {
625
- context.path.push(`[${i}]`);
626
- partialEqualWithErrorCollection(target[i], sub[i], context);
627
- context.path.pop();
628
- }
629
- return;
456
+ return true;
457
+ }
458
+ if (target instanceof RegExp && sub instanceof RegExp) {
459
+ if (target.source !== sub.source || target.flags !== sub.flags) {
460
+ addError(context, {
461
+ message: "RegExp mismatch",
462
+ received: target,
463
+ expected: sub
464
+ });
465
+ return false;
630
466
  }
631
- if (ctor === Set) {
632
- if (sub.size > target.size) {
633
- addError(
634
- context,
635
- `Set too small: expected at least ${sub.size} elements, got ${target.size}`,
636
- target,
637
- sub
638
- );
639
- return;
640
- }
641
- for (const value of sub) {
642
- let found = false;
643
- if (value && typeof value === "object") {
644
- found = !!find(target, value);
645
- } else {
646
- found = target.has(value);
647
- }
648
- if (!found) {
649
- addError(context, `Set missing value`, target, value);
467
+ return true;
468
+ }
469
+ if (target instanceof Set && sub instanceof Set) {
470
+ if (sub.size > target.size) {
471
+ addError(context, {
472
+ message: "Set too small",
473
+ received: target,
474
+ expected: sub
475
+ });
476
+ return false;
477
+ }
478
+ for (const subValue of sub) {
479
+ let found = false;
480
+ for (const targetValue of target) {
481
+ const tempContext = {
482
+ errors: [],
483
+ path: context.path
484
+ };
485
+ if (partialEqualInternal(targetValue, subValue, tempContext)) {
486
+ found = true;
487
+ break;
650
488
  }
651
489
  }
652
- return;
490
+ if (!found) {
491
+ addError(context, {
492
+ message: "Set element not found",
493
+ received: target,
494
+ expected: sub
495
+ });
496
+ return false;
497
+ }
653
498
  }
654
- if (ctor === Map) {
655
- if (sub.size > target.size) {
656
- addError(
657
- context,
658
- `Map too small: expected at least ${sub.size} entries, got ${target.size}`,
659
- target,
660
- sub
661
- );
662
- return;
663
- }
664
- for (const [key, value] of sub) {
665
- let targetKey = key;
666
- if (key && typeof key === "object") {
667
- targetKey = find(target, key);
668
- if (!targetKey) {
669
- addError(context, `Map missing key`, target, key);
670
- continue;
671
- }
672
- }
673
- if (!target.has(targetKey)) {
674
- addError(context, `Map missing key`, target, key);
675
- continue;
499
+ return true;
500
+ }
501
+ if (target instanceof Map && sub instanceof Map) {
502
+ if (sub.size > target.size) {
503
+ addError(context, {
504
+ message: "Map too small",
505
+ received: target,
506
+ expected: sub
507
+ });
508
+ return false;
509
+ }
510
+ for (const [subKey, subValue] of sub) {
511
+ let found = false;
512
+ for (const [targetKey, targetValue] of target) {
513
+ const tempContextKey = {
514
+ errors: [],
515
+ path: context.path
516
+ };
517
+ const tempContextValue = {
518
+ errors: [],
519
+ path: context.path
520
+ };
521
+ if (partialEqualInternal(targetKey, subKey, tempContextKey) && partialEqualInternal(targetValue, subValue, tempContextValue)) {
522
+ found = true;
523
+ break;
676
524
  }
677
- context.path.push(`[${key}]`);
678
- partialEqualWithErrorCollection(target.get(targetKey), value, context);
679
- context.path.pop();
680
525
  }
681
- return;
526
+ if (!found) {
527
+ addError(context, {
528
+ message: "Map entry not found",
529
+ received: target,
530
+ expected: sub
531
+ });
532
+ return false;
533
+ }
682
534
  }
683
- if (!ctor || typeof sub === "object") {
684
- for (const key in sub) {
685
- if (has.call(sub, key)) {
686
- const subValue = sub[key];
687
- context.path.push(key);
688
- if (subValue && typeof subValue === "object" && "~sc" in subValue && subValue["~sc"][0] === "keyNotBePresent") {
689
- if (has.call(target, key)) {
690
- addError(
691
- context,
692
- `Key should not be present`,
693
- target[key],
694
- "key not present"
695
- );
696
- }
697
- } else if (subValue && typeof subValue === "object" && "~sc" in subValue && subValue["~sc"][0] === "any") {
698
- const targetHasKey = has.call(target, key);
699
- const targetValue = targetHasKey ? target[key] : void 0;
700
- executeComparisonWithKeyContextAndErrorCollection(
701
- targetValue,
702
- subValue["~sc"],
703
- targetHasKey,
704
- context
535
+ return true;
536
+ }
537
+ if (typeof target !== typeof sub) {
538
+ addError(context, {
539
+ message: "Value mismatch",
540
+ received: target,
541
+ expected: sub
542
+ });
543
+ return false;
544
+ }
545
+ if (Array.isArray(sub)) {
546
+ if (!Array.isArray(target)) {
547
+ addError(context, {
548
+ message: "Expected array",
549
+ received: target,
550
+ expected: sub
551
+ });
552
+ return false;
553
+ }
554
+ if (target.length < sub.length) {
555
+ addError(context, {
556
+ message: `Array too short: expected at least ${sub.length} elements, got ${target.length}`,
557
+ received: target,
558
+ expected: sub
559
+ });
560
+ return false;
561
+ }
562
+ let allMatch = true;
563
+ for (let i = 0; i < sub.length; i++) {
564
+ const oldPath = context.path;
565
+ context.path = [...oldPath, `[${i}]`];
566
+ const result = partialEqualInternal(target[i], sub[i], context);
567
+ context.path = oldPath;
568
+ if (!result) allMatch = false;
569
+ }
570
+ return allMatch;
571
+ }
572
+ if (typeof sub === "object") {
573
+ if (typeof target !== "object" || Array.isArray(target)) {
574
+ addError(context, {
575
+ message: "Expected object",
576
+ received: target,
577
+ expected: sub
578
+ });
579
+ return false;
580
+ }
581
+ let allMatch = true;
582
+ for (const key of Object.keys(sub)) {
583
+ if (isComparison(sub[key]) && sub[key]["~sc"][0] === "keyNotBePresent") {
584
+ if (has.call(target, key)) {
585
+ const oldPath2 = context.path;
586
+ context.path = [...oldPath2, key];
587
+ addError(context, {
588
+ message: "Key should not be present",
589
+ received: target[key],
590
+ expected: "key not present"
591
+ });
592
+ context.path = oldPath2;
593
+ allMatch = false;
594
+ }
595
+ continue;
596
+ }
597
+ if (!has.call(target, key)) {
598
+ if (isComparison(sub[key])) {
599
+ const comparison = sub[key]["~sc"];
600
+ if (comparison[0] === "any") {
601
+ const anyComparisons = comparison[1];
602
+ const hasKeyNotBePresent = anyComparisons.some(
603
+ (comp) => comp[0] === "keyNotBePresent"
705
604
  );
706
- } else {
707
- if (!has.call(target, key)) {
708
- addError(context, `Missing property`, void 0, subValue);
709
- } else {
710
- partialEqualWithErrorCollection(target[key], subValue, context);
605
+ if (hasKeyNotBePresent) {
606
+ continue;
711
607
  }
712
608
  }
713
- context.path.pop();
714
609
  }
715
- }
716
- return;
610
+ const oldPath2 = context.path;
611
+ context.path = [...oldPath2, key];
612
+ addError(context, {
613
+ message: "Missing property",
614
+ received: void 0,
615
+ expected: sub[key]
616
+ });
617
+ context.path = oldPath2;
618
+ allMatch = false;
619
+ continue;
620
+ }
621
+ const oldPath = context.path;
622
+ context.path = [...oldPath, key];
623
+ const result = partialEqualInternal(target[key], sub[key], context);
624
+ context.path = oldPath;
625
+ if (!result) allMatch = false;
717
626
  }
627
+ return allMatch;
718
628
  }
719
- if (sub !== sub && target !== target) {
720
- return;
629
+ if (target !== sub) {
630
+ addError(context, {
631
+ message: "Value mismatch",
632
+ received: target,
633
+ expected: sub
634
+ });
635
+ return false;
721
636
  }
722
- addError(context, `Value mismatch`, target, sub);
637
+ return true;
723
638
  }
724
- function partialEqual(target, sub, returnErrors) {
725
- if (returnErrors === true) {
726
- const context = {
727
- errors: [],
728
- path: []
729
- };
730
- partialEqualWithErrorCollection(target, sub, context);
731
- if (context.errors.length === 0) {
732
- return ok(void 0);
733
- } else {
734
- return err(context.errors);
735
- }
639
+ function checkNoExtraKeys(target, partialShape, context, deep) {
640
+ if (typeof target !== "object" || target === null || Array.isArray(target)) {
641
+ addError(context, {
642
+ message: "Expected object for key validation",
643
+ received: target
644
+ });
645
+ return false;
736
646
  }
737
- if (sub === target) return true;
738
- if (sub && typeof sub === "object" && "~sc" in sub) {
739
- return executeComparison(target, sub["~sc"]);
647
+ if (!partialEqualInternal(target, partialShape, context)) {
648
+ return false;
740
649
  }
741
- if (sub && target && sub.constructor === target.constructor) {
742
- const ctor = sub.constructor;
743
- if (ctor === Date) {
744
- return sub.getTime() === target.getTime();
745
- }
746
- if (ctor === RegExp) {
747
- return sub.toString() === target.toString();
748
- }
749
- if (ctor === Array) {
750
- if (sub.length > target.length) return false;
751
- for (let i = 0; i < sub.length; i++) {
752
- if (!partialEqual(target[i], sub[i])) return false;
753
- }
754
- return true;
650
+ const allowedKeys = new Set(Object.keys(partialShape));
651
+ for (const key of Object.keys(target)) {
652
+ if (!allowedKeys.has(key)) {
653
+ const oldPath = context.path;
654
+ context.path = [...oldPath, key];
655
+ addError(context, {
656
+ message: `Extra key "${key}" not expected`
657
+ });
658
+ context.path = oldPath;
659
+ return false;
755
660
  }
756
- if (ctor === Set) {
757
- if (sub.size > target.size) return false;
758
- for (const value of sub) {
759
- let found = false;
760
- if (value && typeof value === "object") {
761
- found = !!find(target, value);
762
- } else {
763
- found = target.has(value);
764
- }
765
- if (!found) return false;
661
+ }
662
+ if (deep) {
663
+ for (const key of Object.keys(partialShape)) {
664
+ if (typeof partialShape[key] === "object" && partialShape[key] !== null && !Array.isArray(partialShape[key]) && !isComparison(partialShape[key])) {
665
+ const oldPath = context.path;
666
+ context.path = [...oldPath, key];
667
+ const result = checkNoExtraKeys(
668
+ target[key],
669
+ partialShape[key],
670
+ context,
671
+ true
672
+ );
673
+ context.path = oldPath;
674
+ if (!result) return false;
766
675
  }
767
- return true;
768
676
  }
769
- if (ctor === Map) {
770
- if (sub.size > target.size) return false;
771
- for (const [key, value] of sub) {
772
- let targetKey = key;
773
- if (key && typeof key === "object") {
774
- targetKey = find(target, key);
775
- if (!targetKey) return false;
776
- }
777
- if (!target.has(targetKey) || !partialEqual(target.get(targetKey), value)) {
778
- return false;
779
- }
780
- }
781
- return true;
677
+ }
678
+ return true;
679
+ }
680
+ function checkNoExtraDefinedKeys(target, partialShape, context, deep) {
681
+ if (typeof target !== "object" || target === null || Array.isArray(target)) {
682
+ addError(context, {
683
+ message: "Expected object for key validation",
684
+ received: target
685
+ });
686
+ return false;
687
+ }
688
+ if (!partialEqualInternal(target, partialShape, context)) {
689
+ return false;
690
+ }
691
+ const allowedKeys = new Set(Object.keys(partialShape));
692
+ for (const key of Object.keys(target)) {
693
+ if (!allowedKeys.has(key) && target[key] !== void 0) {
694
+ const oldPath = context.path;
695
+ context.path = [...oldPath, key];
696
+ addError(context, {
697
+ message: `Extra defined key "${key}" not expected`
698
+ });
699
+ context.path = oldPath;
700
+ return false;
782
701
  }
783
- if (!ctor || typeof sub === "object") {
784
- for (const key in sub) {
785
- if (has.call(sub, key)) {
786
- const subValue = sub[key];
787
- if (subValue && typeof subValue === "object" && "~sc" in subValue && subValue["~sc"][0] === "keyNotBePresent") {
788
- if (has.call(target, key)) {
789
- return false;
790
- }
791
- } else if (subValue && typeof subValue === "object" && "~sc" in subValue && subValue["~sc"][0] === "any") {
792
- const targetHasKey = has.call(target, key);
793
- const targetValue = targetHasKey ? target[key] : void 0;
794
- if (!executeComparisonWithKeyContext(
795
- targetValue,
796
- subValue["~sc"],
797
- targetHasKey
798
- )) {
799
- return false;
800
- }
801
- } else {
802
- if (!has.call(target, key) || !partialEqual(target[key], subValue)) {
803
- return false;
804
- }
805
- }
806
- }
702
+ }
703
+ if (deep) {
704
+ for (const key of Object.keys(partialShape)) {
705
+ if (typeof partialShape[key] === "object" && partialShape[key] !== null && !Array.isArray(partialShape[key]) && !isComparison(partialShape[key])) {
706
+ const oldPath = context.path;
707
+ context.path = [...oldPath, key];
708
+ const result = checkNoExtraDefinedKeys(
709
+ target[key],
710
+ partialShape[key],
711
+ context,
712
+ true
713
+ );
714
+ context.path = oldPath;
715
+ if (!result) return false;
807
716
  }
808
- return true;
809
717
  }
810
718
  }
811
- return sub !== sub && target !== target;
719
+ return true;
720
+ }
721
+ function partialEqual(target, sub, returnErrors) {
722
+ const context = { errors: [], path: [] };
723
+ const result = partialEqualInternal(target, sub, context);
724
+ if (returnErrors) {
725
+ return result ? ok(void 0) : err(context.errors);
726
+ }
727
+ return result;
812
728
  }
813
729
  export {
814
730
  match,