@drmxrcy/tcg-core 0.0.0-202602060542

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 (157) hide show
  1. package/README.md +882 -0
  2. package/package.json +58 -0
  3. package/src/__tests__/alpha-clash-engine-definition.test.ts +319 -0
  4. package/src/__tests__/createMockAlphaClashGame.ts +462 -0
  5. package/src/__tests__/createMockGrandArchiveGame.ts +373 -0
  6. package/src/__tests__/createMockGundamGame.ts +379 -0
  7. package/src/__tests__/createMockLorcanaGame.ts +328 -0
  8. package/src/__tests__/createMockOnePieceGame.ts +429 -0
  9. package/src/__tests__/createMockRiftboundGame.ts +462 -0
  10. package/src/__tests__/grand-archive-engine-definition.test.ts +118 -0
  11. package/src/__tests__/gundam-engine-definition.test.ts +110 -0
  12. package/src/__tests__/integration-complete-game.test.ts +508 -0
  13. package/src/__tests__/integration-network-sync.test.ts +469 -0
  14. package/src/__tests__/lorcana-engine-definition.test.ts +100 -0
  15. package/src/__tests__/move-enumeration.test.ts +725 -0
  16. package/src/__tests__/multiplayer-engine.test.ts +555 -0
  17. package/src/__tests__/one-piece-engine-definition.test.ts +114 -0
  18. package/src/__tests__/riftbound-engine-definition.test.ts +124 -0
  19. package/src/actions/action-definition.test.ts +201 -0
  20. package/src/actions/action-definition.ts +122 -0
  21. package/src/actions/action-timing.test.ts +490 -0
  22. package/src/actions/action-timing.ts +257 -0
  23. package/src/cards/card-definition.test.ts +268 -0
  24. package/src/cards/card-definition.ts +27 -0
  25. package/src/cards/card-instance.test.ts +422 -0
  26. package/src/cards/card-instance.ts +49 -0
  27. package/src/cards/computed-properties.test.ts +530 -0
  28. package/src/cards/computed-properties.ts +84 -0
  29. package/src/cards/conditional-modifiers.test.ts +390 -0
  30. package/src/cards/modifiers.test.ts +286 -0
  31. package/src/cards/modifiers.ts +51 -0
  32. package/src/engine/MULTIPLAYER.md +425 -0
  33. package/src/engine/__tests__/rule-engine-flow.test.ts +348 -0
  34. package/src/engine/__tests__/rule-engine-history.test.ts +535 -0
  35. package/src/engine/__tests__/rule-engine-moves.test.ts +488 -0
  36. package/src/engine/__tests__/rule-engine.test.ts +366 -0
  37. package/src/engine/index.ts +14 -0
  38. package/src/engine/multiplayer-engine.example.ts +571 -0
  39. package/src/engine/multiplayer-engine.ts +409 -0
  40. package/src/engine/rule-engine.test.ts +286 -0
  41. package/src/engine/rule-engine.ts +1539 -0
  42. package/src/engine/tracker-system.ts +172 -0
  43. package/src/examples/__tests__/coin-flip-game.test.ts +641 -0
  44. package/src/filtering/card-filter.test.ts +230 -0
  45. package/src/filtering/card-filter.ts +91 -0
  46. package/src/filtering/card-query.test.ts +901 -0
  47. package/src/filtering/card-query.ts +273 -0
  48. package/src/filtering/filter-matching.test.ts +944 -0
  49. package/src/filtering/filter-matching.ts +315 -0
  50. package/src/flow/SERIALIZATION.md +428 -0
  51. package/src/flow/__tests__/flow-definition.test.ts +427 -0
  52. package/src/flow/__tests__/flow-manager.test.ts +756 -0
  53. package/src/flow/__tests__/flow-serialization.test.ts +565 -0
  54. package/src/flow/flow-definition.ts +453 -0
  55. package/src/flow/flow-manager.ts +1044 -0
  56. package/src/flow/index.ts +35 -0
  57. package/src/game-definition/__tests__/game-definition-validation.test.ts +359 -0
  58. package/src/game-definition/__tests__/game-definition.test.ts +291 -0
  59. package/src/game-definition/__tests__/move-definitions.test.ts +328 -0
  60. package/src/game-definition/game-definition.ts +261 -0
  61. package/src/game-definition/index.ts +28 -0
  62. package/src/game-definition/move-definitions.ts +188 -0
  63. package/src/game-definition/validation.ts +183 -0
  64. package/src/history/history-manager.test.ts +497 -0
  65. package/src/history/history-manager.ts +312 -0
  66. package/src/history/history-operations.ts +122 -0
  67. package/src/history/index.ts +9 -0
  68. package/src/history/types.ts +255 -0
  69. package/src/index.ts +32 -0
  70. package/src/logging/index.ts +27 -0
  71. package/src/logging/log-formatter.ts +187 -0
  72. package/src/logging/logger.ts +276 -0
  73. package/src/logging/types.ts +148 -0
  74. package/src/moves/create-move.test.ts +331 -0
  75. package/src/moves/create-move.ts +64 -0
  76. package/src/moves/move-enumeration.ts +228 -0
  77. package/src/moves/move-executor.test.ts +431 -0
  78. package/src/moves/move-executor.ts +195 -0
  79. package/src/moves/move-system.test.ts +380 -0
  80. package/src/moves/move-system.ts +463 -0
  81. package/src/moves/standard-moves.ts +231 -0
  82. package/src/operations/card-operations.test.ts +236 -0
  83. package/src/operations/card-operations.ts +116 -0
  84. package/src/operations/card-registry-impl.test.ts +251 -0
  85. package/src/operations/card-registry-impl.ts +70 -0
  86. package/src/operations/card-registry.test.ts +234 -0
  87. package/src/operations/card-registry.ts +106 -0
  88. package/src/operations/counter-operations.ts +152 -0
  89. package/src/operations/game-operations.test.ts +280 -0
  90. package/src/operations/game-operations.ts +140 -0
  91. package/src/operations/index.ts +24 -0
  92. package/src/operations/operations-impl.test.ts +354 -0
  93. package/src/operations/operations-impl.ts +468 -0
  94. package/src/operations/zone-operations.test.ts +295 -0
  95. package/src/operations/zone-operations.ts +223 -0
  96. package/src/rng/seeded-rng.test.ts +339 -0
  97. package/src/rng/seeded-rng.ts +123 -0
  98. package/src/targeting/index.ts +48 -0
  99. package/src/targeting/target-definition.test.ts +273 -0
  100. package/src/targeting/target-definition.ts +37 -0
  101. package/src/targeting/target-dsl.ts +279 -0
  102. package/src/targeting/target-resolver.ts +486 -0
  103. package/src/targeting/target-validation.test.ts +994 -0
  104. package/src/targeting/target-validation.ts +286 -0
  105. package/src/telemetry/events.ts +202 -0
  106. package/src/telemetry/index.ts +21 -0
  107. package/src/telemetry/telemetry-manager.ts +127 -0
  108. package/src/telemetry/types.ts +68 -0
  109. package/src/testing/__tests__/testing-utilities-integration.test.ts +161 -0
  110. package/src/testing/index.ts +88 -0
  111. package/src/testing/test-assertions.test.ts +341 -0
  112. package/src/testing/test-assertions.ts +256 -0
  113. package/src/testing/test-card-factory.test.ts +228 -0
  114. package/src/testing/test-card-factory.ts +111 -0
  115. package/src/testing/test-context-factory.ts +187 -0
  116. package/src/testing/test-end-assertions.test.ts +262 -0
  117. package/src/testing/test-end-assertions.ts +95 -0
  118. package/src/testing/test-engine-builder.test.ts +389 -0
  119. package/src/testing/test-engine-builder.ts +46 -0
  120. package/src/testing/test-flow-assertions.test.ts +284 -0
  121. package/src/testing/test-flow-assertions.ts +115 -0
  122. package/src/testing/test-player-builder.test.ts +132 -0
  123. package/src/testing/test-player-builder.ts +46 -0
  124. package/src/testing/test-replay-assertions.test.ts +356 -0
  125. package/src/testing/test-replay-assertions.ts +164 -0
  126. package/src/testing/test-rng-helpers.test.ts +260 -0
  127. package/src/testing/test-rng-helpers.ts +190 -0
  128. package/src/testing/test-state-builder.test.ts +373 -0
  129. package/src/testing/test-state-builder.ts +99 -0
  130. package/src/testing/test-zone-factory.test.ts +295 -0
  131. package/src/testing/test-zone-factory.ts +224 -0
  132. package/src/types/branded-utils.ts +54 -0
  133. package/src/types/branded.test.ts +175 -0
  134. package/src/types/branded.ts +33 -0
  135. package/src/types/index.ts +8 -0
  136. package/src/types/state.test.ts +198 -0
  137. package/src/types/state.ts +154 -0
  138. package/src/validation/card-type-guards.test.ts +242 -0
  139. package/src/validation/card-type-guards.ts +179 -0
  140. package/src/validation/index.ts +40 -0
  141. package/src/validation/schema-builders.test.ts +403 -0
  142. package/src/validation/schema-builders.ts +345 -0
  143. package/src/validation/type-guard-builder.test.ts +216 -0
  144. package/src/validation/type-guard-builder.ts +109 -0
  145. package/src/validation/validator-builder.test.ts +375 -0
  146. package/src/validation/validator-builder.ts +273 -0
  147. package/src/zones/index.ts +28 -0
  148. package/src/zones/zone-factory.test.ts +183 -0
  149. package/src/zones/zone-factory.ts +44 -0
  150. package/src/zones/zone-operations.test.ts +800 -0
  151. package/src/zones/zone-operations.ts +306 -0
  152. package/src/zones/zone-state-helpers.test.ts +337 -0
  153. package/src/zones/zone-state-helpers.ts +128 -0
  154. package/src/zones/zone-visibility.test.ts +156 -0
  155. package/src/zones/zone-visibility.ts +36 -0
  156. package/src/zones/zone.test.ts +186 -0
  157. package/src/zones/zone.ts +66 -0
@@ -0,0 +1,373 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { createTestState } from "./test-state-builder";
3
+
4
+ /**
5
+ * Tests for createTestState - State builder for tests
6
+ *
7
+ * Task 2.1: Write tests for test builders (createTestState)
8
+ *
9
+ * Tests verify:
10
+ * - Creating state with default values
11
+ * - Creating state with partial overrides
12
+ * - Creating state with deep overrides
13
+ * - Type safety and inference
14
+ * - Immutability of defaults
15
+ */
16
+
17
+ type TestGameState = {
18
+ turn: number;
19
+ phase: "setup" | "play" | "end";
20
+ players: Array<{
21
+ id: string;
22
+ name: string;
23
+ health: number;
24
+ }>;
25
+ deck: string[];
26
+ };
27
+
28
+ describe("createTestState", () => {
29
+ describe("Basic Functionality", () => {
30
+ it("should create state with defaults", () => {
31
+ const defaults: TestGameState = {
32
+ turn: 1,
33
+ phase: "setup",
34
+ players: [],
35
+ deck: [],
36
+ };
37
+
38
+ const state = createTestState(defaults);
39
+
40
+ expect(state).toEqual(defaults);
41
+ expect(state.turn).toBe(1);
42
+ expect(state.phase).toBe("setup");
43
+ expect(state.players).toHaveLength(0);
44
+ expect(state.deck).toHaveLength(0);
45
+ });
46
+
47
+ it("should override specific fields", () => {
48
+ const defaults: TestGameState = {
49
+ turn: 1,
50
+ phase: "setup",
51
+ players: [],
52
+ deck: [],
53
+ };
54
+
55
+ const state = createTestState(defaults, {
56
+ turn: 5,
57
+ phase: "play",
58
+ });
59
+
60
+ expect(state.turn).toBe(5);
61
+ expect(state.phase).toBe("play");
62
+ expect(state.players).toHaveLength(0);
63
+ expect(state.deck).toHaveLength(0);
64
+ });
65
+
66
+ it("should override nested fields", () => {
67
+ const defaults: TestGameState = {
68
+ turn: 1,
69
+ phase: "setup",
70
+ players: [
71
+ { id: "p1", name: "Player 1", health: 20 },
72
+ { id: "p2", name: "Player 2", health: 20 },
73
+ ],
74
+ deck: ["card1", "card2"],
75
+ };
76
+
77
+ const state = createTestState(defaults, {
78
+ players: [
79
+ { id: "p1", name: "Alice", health: 15 },
80
+ { id: "p2", name: "Bob", health: 18 },
81
+ ],
82
+ });
83
+
84
+ expect(state.players[0]?.name).toBe("Alice");
85
+ expect(state.players[0]?.health).toBe(15);
86
+ expect(state.players[1]?.name).toBe("Bob");
87
+ expect(state.players[1]?.health).toBe(18);
88
+ expect(state.turn).toBe(1);
89
+ expect(state.phase).toBe("setup");
90
+ });
91
+
92
+ it("should override array fields", () => {
93
+ const defaults: TestGameState = {
94
+ turn: 1,
95
+ phase: "setup",
96
+ players: [],
97
+ deck: ["card1", "card2"],
98
+ };
99
+
100
+ const state = createTestState(defaults, {
101
+ deck: ["card3", "card4", "card5"],
102
+ });
103
+
104
+ expect(state.deck).toEqual(["card3", "card4", "card5"]);
105
+ expect(state.turn).toBe(1);
106
+ });
107
+ });
108
+
109
+ describe("Immutability", () => {
110
+ it("should not modify defaults when creating state", () => {
111
+ const defaults: TestGameState = {
112
+ turn: 1,
113
+ phase: "setup",
114
+ players: [{ id: "p1", name: "Player 1", health: 20 }],
115
+ deck: ["card1"],
116
+ };
117
+
118
+ createTestState(defaults, {
119
+ turn: 2,
120
+ players: [{ id: "p1", name: "Modified", health: 10 }],
121
+ });
122
+
123
+ // Defaults should remain unchanged
124
+ expect(defaults.turn).toBe(1);
125
+ expect(defaults.players[0]?.name).toBe("Player 1");
126
+ expect(defaults.players[0]?.health).toBe(20);
127
+ });
128
+
129
+ it("should not modify defaults when creating multiple states", () => {
130
+ const defaults: TestGameState = {
131
+ turn: 1,
132
+ phase: "setup",
133
+ players: [],
134
+ deck: [],
135
+ };
136
+
137
+ const state1 = createTestState(defaults, { turn: 2 });
138
+ const state2 = createTestState(defaults, { turn: 3 });
139
+
140
+ expect(defaults.turn).toBe(1);
141
+ expect(state1.turn).toBe(2);
142
+ expect(state2.turn).toBe(3);
143
+ });
144
+
145
+ it("should create independent state copies", () => {
146
+ const defaults: TestGameState = {
147
+ turn: 1,
148
+ phase: "setup",
149
+ players: [{ id: "p1", name: "Player 1", health: 20 }],
150
+ deck: ["card1"],
151
+ };
152
+
153
+ const state1 = createTestState(defaults);
154
+ const state2 = createTestState(defaults);
155
+
156
+ // Modify state1
157
+ state1.players[0]!.health = 10;
158
+ state1.deck.push("card2");
159
+
160
+ // state2 should be unaffected
161
+ expect(state2.players[0]?.health).toBe(20);
162
+ expect(state2.deck).toHaveLength(1);
163
+ });
164
+ });
165
+
166
+ describe("Type Safety", () => {
167
+ it("should infer correct type from defaults", () => {
168
+ type SimpleState = {
169
+ value: number;
170
+ label: string;
171
+ };
172
+
173
+ const defaults: SimpleState = {
174
+ value: 0,
175
+ label: "test",
176
+ };
177
+
178
+ const state = createTestState(defaults, { value: 42 });
179
+
180
+ // TypeScript should infer state as SimpleState
181
+ expect(state.value).toBe(42);
182
+ expect(state.label).toBe("test");
183
+ });
184
+
185
+ it("should allow partial overrides", () => {
186
+ const defaults: TestGameState = {
187
+ turn: 1,
188
+ phase: "setup",
189
+ players: [],
190
+ deck: [],
191
+ };
192
+
193
+ // Should accept partial overrides without type errors
194
+ const state1 = createTestState(defaults, { turn: 5 });
195
+ const state2 = createTestState(defaults, { phase: "play" });
196
+ const state3 = createTestState(defaults, {});
197
+
198
+ expect(state1.turn).toBe(5);
199
+ expect(state2.phase).toBe("play");
200
+ expect(state3).toEqual(defaults);
201
+ });
202
+ });
203
+
204
+ describe("Edge Cases", () => {
205
+ it("should handle empty overrides", () => {
206
+ const defaults: TestGameState = {
207
+ turn: 1,
208
+ phase: "setup",
209
+ players: [],
210
+ deck: [],
211
+ };
212
+
213
+ const state = createTestState(defaults, {});
214
+
215
+ expect(state).toEqual(defaults);
216
+ });
217
+
218
+ it("should handle undefined overrides", () => {
219
+ const defaults: TestGameState = {
220
+ turn: 1,
221
+ phase: "setup",
222
+ players: [],
223
+ deck: [],
224
+ };
225
+
226
+ const state = createTestState(defaults);
227
+
228
+ expect(state).toEqual(defaults);
229
+ });
230
+
231
+ it("should handle complex nested structures", () => {
232
+ type ComplexState = {
233
+ meta: {
234
+ gameId: string;
235
+ created: number;
236
+ };
237
+ config: {
238
+ rules: {
239
+ maxPlayers: number;
240
+ turnLimit: number;
241
+ };
242
+ };
243
+ };
244
+
245
+ const defaults: ComplexState = {
246
+ meta: {
247
+ gameId: "game-1",
248
+ created: 1000,
249
+ },
250
+ config: {
251
+ rules: {
252
+ maxPlayers: 4,
253
+ turnLimit: 100,
254
+ },
255
+ },
256
+ };
257
+
258
+ const state = createTestState(defaults, {
259
+ config: {
260
+ rules: {
261
+ maxPlayers: 6,
262
+ turnLimit: 200,
263
+ },
264
+ },
265
+ });
266
+
267
+ expect(state.config.rules.maxPlayers).toBe(6);
268
+ expect(state.config.rules.turnLimit).toBe(200);
269
+ expect(state.meta.gameId).toBe("game-1");
270
+ });
271
+
272
+ it("should handle arrays of primitives", () => {
273
+ type ArrayState = {
274
+ numbers: number[];
275
+ strings: string[];
276
+ booleans: boolean[];
277
+ };
278
+
279
+ const defaults: ArrayState = {
280
+ numbers: [1, 2, 3],
281
+ strings: ["a", "b"],
282
+ booleans: [true, false],
283
+ };
284
+
285
+ const state = createTestState(defaults, {
286
+ numbers: [4, 5, 6, 7],
287
+ strings: ["x"],
288
+ });
289
+
290
+ expect(state.numbers).toEqual([4, 5, 6, 7]);
291
+ expect(state.strings).toEqual(["x"]);
292
+ expect(state.booleans).toEqual([true, false]);
293
+ });
294
+
295
+ it("should handle null and undefined values", () => {
296
+ type NullableState = {
297
+ optional?: string;
298
+ nullable: string | null;
299
+ value: number;
300
+ };
301
+
302
+ const defaults: NullableState = {
303
+ optional: "default",
304
+ nullable: null,
305
+ value: 0,
306
+ };
307
+
308
+ const state = createTestState(defaults, {
309
+ optional: undefined,
310
+ nullable: "not-null",
311
+ });
312
+
313
+ expect(state.optional).toBeUndefined();
314
+ expect(state.nullable).toBe("not-null");
315
+ expect(state.value).toBe(0);
316
+ });
317
+ });
318
+
319
+ describe("Practical Test Scenarios", () => {
320
+ it("should simplify test setup for game states", () => {
321
+ const defaults: TestGameState = {
322
+ turn: 1,
323
+ phase: "setup",
324
+ players: [
325
+ { id: "p1", name: "Player 1", health: 20 },
326
+ { id: "p2", name: "Player 2", health: 20 },
327
+ ],
328
+ deck: Array.from({ length: 40 }, (_, i) => `card${i}`),
329
+ };
330
+
331
+ // Test scenario: mid-game state
332
+ const midGameState = createTestState(defaults, {
333
+ turn: 5,
334
+ phase: "play",
335
+ deck: defaults.deck.slice(10),
336
+ });
337
+
338
+ expect(midGameState.turn).toBe(5);
339
+ expect(midGameState.phase).toBe("play");
340
+ expect(midGameState.deck).toHaveLength(30);
341
+ expect(midGameState.players).toHaveLength(2);
342
+ });
343
+
344
+ it("should support testing edge cases with minimal setup", () => {
345
+ const defaults: TestGameState = {
346
+ turn: 1,
347
+ phase: "setup",
348
+ players: [
349
+ { id: "p1", name: "Player 1", health: 20 },
350
+ { id: "p2", name: "Player 2", health: 20 },
351
+ ],
352
+ deck: ["card1", "card2"],
353
+ };
354
+
355
+ // Test scenario: player at low health
356
+ const lowHealthState = createTestState(defaults, {
357
+ players: [
358
+ { id: "p1", name: "Player 1", health: 1 },
359
+ { id: "p2", name: "Player 2", health: 20 },
360
+ ],
361
+ });
362
+
363
+ expect(lowHealthState.players[0]?.health).toBe(1);
364
+
365
+ // Test scenario: empty deck
366
+ const emptyDeckState = createTestState(defaults, {
367
+ deck: [],
368
+ });
369
+
370
+ expect(emptyDeckState.deck).toHaveLength(0);
371
+ });
372
+ });
373
+ });
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Test State Builder
3
+ *
4
+ * Task 2.5: Implement createTestState<T>(overrides?)
5
+ *
6
+ * Creates test state objects with defaults and selective overrides.
7
+ * Simplifies test setup by allowing partial state specifications.
8
+ *
9
+ * Features:
10
+ * - Deep merge of defaults and overrides
11
+ * - Type-safe: overrides must match state structure
12
+ * - Immutable: doesn't modify defaults
13
+ * - Supports nested objects and arrays
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const defaults = {
18
+ * turn: 1,
19
+ * phase: 'setup',
20
+ * players: [
21
+ * { id: 'p1', health: 20 }
22
+ * ]
23
+ * };
24
+ *
25
+ * // Use defaults
26
+ * const state = createTestState(defaults);
27
+ *
28
+ * // Override specific fields
29
+ * const midGameState = createTestState(defaults, {
30
+ * turn: 5,
31
+ * phase: 'play'
32
+ * });
33
+ *
34
+ * // Override nested fields
35
+ * const lowHealthState = createTestState(defaults, {
36
+ * players: [{ id: 'p1', health: 1 }]
37
+ * });
38
+ * ```
39
+ */
40
+ export function createTestState<T>(defaults: T, overrides?: Partial<T>): T {
41
+ // Use structuredClone for deep cloning to ensure immutability
42
+ // This preserves complex types (Date, Map, Set, etc.) better than JSON
43
+ const clonedDefaults = structuredClone(defaults);
44
+
45
+ if (!overrides) {
46
+ return clonedDefaults;
47
+ }
48
+
49
+ // Deep merge overrides into cloned defaults
50
+ return deepMerge(clonedDefaults, overrides);
51
+ }
52
+
53
+ /**
54
+ * Deep merge utility
55
+ *
56
+ * Recursively merges source into target.
57
+ * Arrays are replaced entirely, not merged element-wise.
58
+ *
59
+ * @internal
60
+ */
61
+ function deepMerge<T>(target: T, source: Partial<T>): T {
62
+ const result = { ...target };
63
+
64
+ for (const key in source) {
65
+ if (Object.hasOwn(source, key)) {
66
+ const sourceValue = source[key];
67
+ const targetValue = target[key];
68
+
69
+ if (sourceValue === undefined) {
70
+ // Allow explicit undefined to override
71
+ // biome-ignore lint/suspicious/noExplicitAny: Safe type assertion for deep merge
72
+ result[key] = sourceValue as any;
73
+ } else if (
74
+ isObject(sourceValue) &&
75
+ isObject(targetValue) &&
76
+ !Array.isArray(sourceValue)
77
+ ) {
78
+ // Recursively merge objects (but not arrays)
79
+ // biome-ignore lint/suspicious/noExplicitAny: Safe type assertion for deep merge
80
+ result[key] = deepMerge(targetValue, sourceValue as any) as any;
81
+ } else {
82
+ // Replace primitives, arrays, null, etc.
83
+ // biome-ignore lint/suspicious/noExplicitAny: Safe type assertion for deep merge
84
+ result[key] = sourceValue as any;
85
+ }
86
+ }
87
+ }
88
+
89
+ return result;
90
+ }
91
+
92
+ /**
93
+ * Type guard for objects
94
+ *
95
+ * @internal
96
+ */
97
+ function isObject(value: unknown): value is Record<string, unknown> {
98
+ return typeof value === "object" && value !== null;
99
+ }