@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,375 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { ValidatorBuilder } from "./validator-builder";
3
+
4
+ describe("ValidatorBuilder", () => {
5
+ describe("required validation", () => {
6
+ it("should validate required fields", () => {
7
+ const validator = new ValidatorBuilder<{ name: string }>()
8
+ .required("name", "Name is required")
9
+ .build();
10
+
11
+ const validResult = validator.validate({ name: "Test" });
12
+ expect(validResult.success).toBe(true);
13
+ if (validResult.success) {
14
+ expect(validResult.data).toEqual({ name: "Test" });
15
+ }
16
+
17
+ const invalidResult = validator.validate({ name: "" });
18
+ expect(invalidResult.success).toBe(false);
19
+ if (!invalidResult.success) {
20
+ expect(invalidResult.errors).toContain("Name is required");
21
+ }
22
+ });
23
+
24
+ it("should validate multiple required fields", () => {
25
+ const validator = new ValidatorBuilder<{ name: string; type: string }>()
26
+ .required("name", "Name is required")
27
+ .required("type", "Type is required")
28
+ .build();
29
+
30
+ const validResult = validator.validate({
31
+ name: "Test",
32
+ type: "creature",
33
+ });
34
+ expect(validResult.success).toBe(true);
35
+
36
+ const invalidResult = validator.validate({ name: "", type: "" });
37
+ expect(invalidResult.success).toBe(false);
38
+ if (!invalidResult.success) {
39
+ expect(invalidResult.errors).toHaveLength(2);
40
+ }
41
+ });
42
+ });
43
+
44
+ describe("type validation", () => {
45
+ it("should validate string types", () => {
46
+ const validator = new ValidatorBuilder<{ name: string }>()
47
+ .type("name", "string", "Name must be a string")
48
+ .build();
49
+
50
+ const validResult = validator.validate({ name: "Test" });
51
+ expect(validResult.success).toBe(true);
52
+
53
+ const invalidResult = validator.validate({ name: 123 as any });
54
+ expect(invalidResult.success).toBe(false);
55
+ if (!invalidResult.success) {
56
+ expect(invalidResult.errors).toContain("Name must be a string");
57
+ }
58
+ });
59
+
60
+ it("should validate number types", () => {
61
+ const validator = new ValidatorBuilder<{ power: number }>()
62
+ .type("power", "number", "Power must be a number")
63
+ .build();
64
+
65
+ const validResult = validator.validate({ power: 5 });
66
+ expect(validResult.success).toBe(true);
67
+
68
+ const invalidResult = validator.validate({ power: "5" as any });
69
+ expect(invalidResult.success).toBe(false);
70
+ if (!invalidResult.success) {
71
+ expect(invalidResult.errors).toContain("Power must be a number");
72
+ }
73
+ });
74
+
75
+ it("should validate boolean types", () => {
76
+ const validator = new ValidatorBuilder<{ enabled: boolean }>()
77
+ .type("enabled", "boolean", "Enabled must be a boolean")
78
+ .build();
79
+
80
+ const validResult = validator.validate({ enabled: true });
81
+ expect(validResult.success).toBe(true);
82
+
83
+ const invalidResult = validator.validate({ enabled: "true" as any });
84
+ expect(invalidResult.success).toBe(false);
85
+ });
86
+ });
87
+
88
+ describe("min/max validation", () => {
89
+ it("should validate minimum values for numbers", () => {
90
+ const validator = new ValidatorBuilder<{ power: number }>()
91
+ .min("power", 0, "Power must be at least 0")
92
+ .build();
93
+
94
+ const validResult = validator.validate({ power: 5 });
95
+ expect(validResult.success).toBe(true);
96
+
97
+ const invalidResult = validator.validate({ power: -1 });
98
+ expect(invalidResult.success).toBe(false);
99
+ if (!invalidResult.success) {
100
+ expect(invalidResult.errors).toContain("Power must be at least 0");
101
+ }
102
+ });
103
+
104
+ it("should validate maximum values for numbers", () => {
105
+ const validator = new ValidatorBuilder<{ power: number }>()
106
+ .max("power", 10, "Power must be at most 10")
107
+ .build();
108
+
109
+ const validResult = validator.validate({ power: 5 });
110
+ expect(validResult.success).toBe(true);
111
+
112
+ const invalidResult = validator.validate({ power: 15 });
113
+ expect(invalidResult.success).toBe(false);
114
+ if (!invalidResult.success) {
115
+ expect(invalidResult.errors).toContain("Power must be at most 10");
116
+ }
117
+ });
118
+
119
+ it("should validate min and max together", () => {
120
+ const validator = new ValidatorBuilder<{ power: number }>()
121
+ .min("power", 0, "Power must be at least 0")
122
+ .max("power", 10, "Power must be at most 10")
123
+ .build();
124
+
125
+ expect(validator.validate({ power: 5 }).success).toBe(true);
126
+ expect(validator.validate({ power: 0 }).success).toBe(true);
127
+ expect(validator.validate({ power: 10 }).success).toBe(true);
128
+ expect(validator.validate({ power: -1 }).success).toBe(false);
129
+ expect(validator.validate({ power: 11 }).success).toBe(false);
130
+ });
131
+
132
+ it("should validate string length with min", () => {
133
+ const validator = new ValidatorBuilder<{ name: string }>()
134
+ .min("name", 3, "Name must be at least 3 characters")
135
+ .build();
136
+
137
+ expect(validator.validate({ name: "Test" }).success).toBe(true);
138
+ expect(validator.validate({ name: "Hi" }).success).toBe(false);
139
+ });
140
+
141
+ it("should validate string length with max", () => {
142
+ const validator = new ValidatorBuilder<{ name: string }>()
143
+ .max("name", 10, "Name must be at most 10 characters")
144
+ .build();
145
+
146
+ expect(validator.validate({ name: "Short" }).success).toBe(true);
147
+ expect(validator.validate({ name: "Very Long Name" }).success).toBe(
148
+ false,
149
+ );
150
+ });
151
+ });
152
+
153
+ describe("custom validation", () => {
154
+ it("should support custom validation functions", () => {
155
+ const validator = new ValidatorBuilder<{ email: string }>()
156
+ .custom(
157
+ "email",
158
+ (value) => typeof value === "string" && value.includes("@"),
159
+ "Email must contain @",
160
+ )
161
+ .build();
162
+
163
+ const validResult = validator.validate({ email: "test@example.com" });
164
+ expect(validResult.success).toBe(true);
165
+
166
+ const invalidResult = validator.validate({ email: "invalid-email" });
167
+ expect(invalidResult.success).toBe(false);
168
+ if (!invalidResult.success) {
169
+ expect(invalidResult.errors).toContain("Email must contain @");
170
+ }
171
+ });
172
+
173
+ it("should support multiple custom validators on the same field", () => {
174
+ const validator = new ValidatorBuilder<{ password: string }>()
175
+ .custom(
176
+ "password",
177
+ (value) => typeof value === "string" && value.length >= 8,
178
+ "Password must be at least 8 characters",
179
+ )
180
+ .custom(
181
+ "password",
182
+ (value) => typeof value === "string" && /[A-Z]/.test(value),
183
+ "Password must contain an uppercase letter",
184
+ )
185
+ .build();
186
+
187
+ expect(validator.validate({ password: "Password1" }).success).toBe(true);
188
+ expect(validator.validate({ password: "short" }).success).toBe(false);
189
+ expect(validator.validate({ password: "lowercase1" }).success).toBe(
190
+ false,
191
+ );
192
+ });
193
+ });
194
+
195
+ describe("fluent API", () => {
196
+ it("should chain multiple validations", () => {
197
+ const validator = new ValidatorBuilder<{
198
+ name: string;
199
+ power: number;
200
+ type: string;
201
+ }>()
202
+ .required("name", "Name is required")
203
+ .type("name", "string", "Name must be a string")
204
+ .min("name", 1, "Name must not be empty")
205
+ .max("name", 50, "Name must be at most 50 characters")
206
+ .required("power", "Power is required")
207
+ .type("power", "number", "Power must be a number")
208
+ .min("power", 0, "Power must be non-negative")
209
+ .max("power", 100, "Power must be at most 100")
210
+ .required("type", "Type is required")
211
+ .build();
212
+
213
+ const validResult = validator.validate({
214
+ name: "Dragon",
215
+ power: 50,
216
+ type: "creature",
217
+ });
218
+ expect(validResult.success).toBe(true);
219
+
220
+ const invalidResult = validator.validate({
221
+ name: "",
222
+ power: 150,
223
+ type: "",
224
+ });
225
+ expect(invalidResult.success).toBe(false);
226
+ });
227
+ });
228
+
229
+ describe("error collection", () => {
230
+ it("should collect all validation errors", () => {
231
+ const validator = new ValidatorBuilder<{
232
+ name: string;
233
+ power: number;
234
+ type: string;
235
+ }>()
236
+ .required("name", "Name is required")
237
+ .required("power", "Power is required")
238
+ .required("type", "Type is required")
239
+ .build();
240
+
241
+ const result = validator.validate({
242
+ name: "",
243
+ power: 0,
244
+ type: "",
245
+ });
246
+
247
+ expect(result.success).toBe(false);
248
+ if (!result.success) {
249
+ expect(result.errors.length).toBeGreaterThan(0);
250
+ }
251
+ });
252
+
253
+ it("should stop at first error when configured", () => {
254
+ const validator = new ValidatorBuilder<{
255
+ name: string;
256
+ power: number;
257
+ }>({ abortEarly: true })
258
+ .required("name", "Name is required")
259
+ .required("power", "Power is required")
260
+ .build();
261
+
262
+ const result = validator.validate({
263
+ name: "",
264
+ power: 0,
265
+ });
266
+
267
+ expect(result.success).toBe(false);
268
+ if (!result.success) {
269
+ // With abortEarly, should only have one error
270
+ expect(result.errors).toHaveLength(1);
271
+ }
272
+ });
273
+ });
274
+
275
+ describe("complex object validation", () => {
276
+ it("should validate nested object structures", () => {
277
+ type Card = {
278
+ name: string;
279
+ metadata: {
280
+ version: number;
281
+ category: string;
282
+ };
283
+ };
284
+
285
+ const validator = new ValidatorBuilder<Card>()
286
+ .required("name", "Name is required")
287
+ .custom(
288
+ "metadata",
289
+ (value) => typeof value === "object" && value !== null,
290
+ "Metadata is required",
291
+ )
292
+ .build();
293
+
294
+ const validResult = validator.validate({
295
+ name: "Dragon",
296
+ metadata: { version: 1, category: "creature" },
297
+ });
298
+ expect(validResult.success).toBe(true);
299
+ });
300
+
301
+ it("should validate arrays", () => {
302
+ type Card = {
303
+ abilities: string[];
304
+ };
305
+
306
+ const validator = new ValidatorBuilder<Card>()
307
+ .custom(
308
+ "abilities",
309
+ (value) => Array.isArray(value) && value.length > 0,
310
+ "Must have at least one ability",
311
+ )
312
+ .build();
313
+
314
+ expect(
315
+ validator.validate({
316
+ abilities: ["Flying", "Haste"],
317
+ }).success,
318
+ ).toBe(true);
319
+
320
+ expect(
321
+ validator.validate({
322
+ abilities: [],
323
+ }).success,
324
+ ).toBe(false);
325
+ });
326
+ });
327
+
328
+ describe("reusability", () => {
329
+ it("should allow reusing validators", () => {
330
+ const cardValidator = new ValidatorBuilder<{
331
+ name: string;
332
+ type: string;
333
+ }>()
334
+ .required("name", "Name is required")
335
+ .required("type", "Type is required")
336
+ .build();
337
+
338
+ const card1 = { name: "Dragon", type: "creature" };
339
+ const card2 = { name: "Bolt", type: "instant" };
340
+ const card3 = { name: "", type: "" };
341
+
342
+ expect(cardValidator.validate(card1).success).toBe(true);
343
+ expect(cardValidator.validate(card2).success).toBe(true);
344
+ expect(cardValidator.validate(card3).success).toBe(false);
345
+ });
346
+ });
347
+
348
+ describe("performance", () => {
349
+ it("should validate efficiently", () => {
350
+ const validator = new ValidatorBuilder<{
351
+ name: string;
352
+ power: number;
353
+ type: string;
354
+ }>()
355
+ .required("name", "Name is required")
356
+ .type("name", "string", "Name must be a string")
357
+ .required("power", "Power is required")
358
+ .type("power", "number", "Power must be a number")
359
+ .min("power", 0, "Power must be non-negative")
360
+ .required("type", "Type is required")
361
+ .build();
362
+
363
+ const card = { name: "Dragon", power: 5, type: "creature" };
364
+
365
+ const startTime = performance.now();
366
+ for (let i = 0; i < 10000; i++) {
367
+ validator.validate(card);
368
+ }
369
+ const endTime = performance.now();
370
+
371
+ // Should complete in reasonable time (< 1000ms for 10k validations, higher threshold for CI parallel execution)
372
+ expect(endTime - startTime).toBeLessThan(1000);
373
+ });
374
+ });
375
+ });
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Validator builder for creating fluent validation rules
3
+ *
4
+ * Provides a chainable API for building complex validation logic
5
+ * without external dependencies like Zod, useful for runtime validation.
6
+ */
7
+
8
+ /**
9
+ * Validation rule function type
10
+ */
11
+ type ValidationRule<T> = (value: T) => boolean;
12
+
13
+ /**
14
+ * Validation error with field and message
15
+ */
16
+ interface ValidationError {
17
+ field: string;
18
+ message: string;
19
+ }
20
+
21
+ /**
22
+ * Validation result - success with data or failure with errors
23
+ */
24
+ export type ValidationResult<T> =
25
+ | { success: true; data: T }
26
+ | { success: false; errors: string[] };
27
+
28
+ /**
29
+ * Validator function type
30
+ */
31
+ export interface Validator<T> {
32
+ validate: (data: T) => ValidationResult<T>;
33
+ }
34
+
35
+ /**
36
+ * Options for validator builder
37
+ */
38
+ interface ValidatorBuilderOptions {
39
+ /**
40
+ * If true, stops validation at the first error
41
+ * If false, collects all errors
42
+ */
43
+ abortEarly?: boolean;
44
+ }
45
+
46
+ /**
47
+ * Internal validation rule with metadata
48
+ */
49
+ interface ValidationRuleWithMetadata<T> {
50
+ field: keyof T;
51
+ rule: ValidationRule<unknown>;
52
+ message: string;
53
+ }
54
+
55
+ /**
56
+ * ValidatorBuilder provides a fluent API for building validation rules
57
+ *
58
+ * @template T - The type of object to validate
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * const validator = new ValidatorBuilder<{ name: string; power: number }>()
63
+ * .required("name", "Name is required")
64
+ * .type("name", "string", "Name must be a string")
65
+ * .min("name", 1, "Name must not be empty")
66
+ * .required("power", "Power is required")
67
+ * .type("power", "number", "Power must be a number")
68
+ * .min("power", 0, "Power must be non-negative")
69
+ * .build();
70
+ *
71
+ * const result = validator.validate({ name: "Dragon", power: 5 });
72
+ * if (result.success) {
73
+ * console.log("Valid:", result.data);
74
+ * } else {
75
+ * console.error("Errors:", result.errors);
76
+ * }
77
+ * ```
78
+ */
79
+ export class ValidatorBuilder<T extends Record<string, unknown>> {
80
+ private rules: ValidationRuleWithMetadata<T>[] = [];
81
+ private options: ValidatorBuilderOptions;
82
+
83
+ constructor(options: ValidatorBuilderOptions = {}) {
84
+ this.options = {
85
+ abortEarly: false,
86
+ ...options,
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Validates that a field is required (not empty/null/undefined)
92
+ *
93
+ * @param field - The field to validate
94
+ * @param message - Error message if validation fails
95
+ * @returns This builder instance for chaining
96
+ */
97
+ required<K extends keyof T>(field: K, message: string): this {
98
+ this.rules.push({
99
+ field,
100
+ rule: (value: unknown) => {
101
+ if (typeof value === "string") {
102
+ return value.trim().length > 0;
103
+ }
104
+ if (typeof value === "number") {
105
+ return true; // Numbers are always considered "present"
106
+ }
107
+ return value !== null && value !== undefined;
108
+ },
109
+ message,
110
+ });
111
+ return this;
112
+ }
113
+
114
+ /**
115
+ * Validates that a field is of a specific type
116
+ *
117
+ * @param field - The field to validate
118
+ * @param expectedType - The expected type ("string", "number", "boolean", "object")
119
+ * @param message - Error message if validation fails
120
+ * @returns This builder instance for chaining
121
+ */
122
+ type<K extends keyof T>(
123
+ field: K,
124
+ expectedType: "string" | "number" | "boolean" | "object",
125
+ message: string,
126
+ ): this {
127
+ this.rules.push({
128
+ field,
129
+ rule: (value: unknown) => typeof value === expectedType,
130
+ message,
131
+ });
132
+ return this;
133
+ }
134
+
135
+ /**
136
+ * Validates that a numeric field or string length is at least a minimum value
137
+ *
138
+ * @param field - The field to validate
139
+ * @param minValue - The minimum value (for numbers) or length (for strings)
140
+ * @param message - Error message if validation fails
141
+ * @returns This builder instance for chaining
142
+ */
143
+ min<K extends keyof T>(field: K, minValue: number, message: string): this {
144
+ this.rules.push({
145
+ field,
146
+ rule: (value: unknown) => {
147
+ if (typeof value === "number") {
148
+ return value >= minValue;
149
+ }
150
+ if (typeof value === "string") {
151
+ return value.length >= minValue;
152
+ }
153
+ return false;
154
+ },
155
+ message,
156
+ });
157
+ return this;
158
+ }
159
+
160
+ /**
161
+ * Validates that a numeric field or string length is at most a maximum value
162
+ *
163
+ * @param field - The field to validate
164
+ * @param maxValue - The maximum value (for numbers) or length (for strings)
165
+ * @param message - Error message if validation fails
166
+ * @returns This builder instance for chaining
167
+ */
168
+ max<K extends keyof T>(field: K, maxValue: number, message: string): this {
169
+ this.rules.push({
170
+ field,
171
+ rule: (value: unknown) => {
172
+ if (typeof value === "number") {
173
+ return value <= maxValue;
174
+ }
175
+ if (typeof value === "string") {
176
+ return value.length <= maxValue;
177
+ }
178
+ return false;
179
+ },
180
+ message,
181
+ });
182
+ return this;
183
+ }
184
+
185
+ /**
186
+ * Adds a custom validation rule
187
+ *
188
+ * @param field - The field to validate
189
+ * @param rule - Custom validation function that returns true if valid
190
+ * @param message - Error message if validation fails
191
+ * @returns This builder instance for chaining
192
+ */
193
+ custom<K extends keyof T>(
194
+ field: K,
195
+ rule: (value: T[K]) => boolean,
196
+ message: string,
197
+ ): this {
198
+ this.rules.push({
199
+ field,
200
+ rule: rule as ValidationRule<unknown>,
201
+ message,
202
+ });
203
+ return this;
204
+ }
205
+
206
+ /**
207
+ * Builds the validator from the accumulated rules
208
+ *
209
+ * @returns A validator object with a validate method
210
+ */
211
+ build(): Validator<T> {
212
+ const rules = [...this.rules];
213
+ const abortEarly = this.options.abortEarly;
214
+
215
+ return {
216
+ validate: (data: T): ValidationResult<T> => {
217
+ const errors: string[] = [];
218
+
219
+ for (const rule of rules) {
220
+ const fieldValue = data[rule.field];
221
+ const isValid = rule.rule(fieldValue);
222
+
223
+ if (!isValid) {
224
+ errors.push(rule.message);
225
+
226
+ if (abortEarly) {
227
+ return {
228
+ success: false,
229
+ errors,
230
+ };
231
+ }
232
+ }
233
+ }
234
+
235
+ if (errors.length > 0) {
236
+ return {
237
+ success: false,
238
+ errors,
239
+ };
240
+ }
241
+
242
+ return {
243
+ success: true,
244
+ data,
245
+ };
246
+ },
247
+ };
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Helper function to create a validator with a more functional style
253
+ *
254
+ * @template T - The type of object to validate
255
+ * @param builderFn - Function that receives a builder and returns it with rules applied
256
+ * @returns A validator object
257
+ *
258
+ * @example
259
+ * ```typescript
260
+ * const validator = createValidator<{ name: string; power: number }>(builder =>
261
+ * builder
262
+ * .required("name", "Name is required")
263
+ * .min("power", 0, "Power must be non-negative")
264
+ * );
265
+ * ```
266
+ */
267
+ export function createValidator<T extends Record<string, unknown>>(
268
+ builderFn: (builder: ValidatorBuilder<T>) => ValidatorBuilder<T>,
269
+ options?: ValidatorBuilderOptions,
270
+ ): Validator<T> {
271
+ const builder = new ValidatorBuilder<T>(options);
272
+ return builderFn(builder).build();
273
+ }
@@ -0,0 +1,28 @@
1
+ export type { CardZoneConfig, Zone, ZoneVisibility } from "./zone";
2
+ export { createZone } from "./zone-factory";
3
+ export {
4
+ addCard,
5
+ addCardToBottom,
6
+ addCardToTop,
7
+ clearZone,
8
+ draw,
9
+ findCardInZones,
10
+ getBottomCard,
11
+ getCardsInZone,
12
+ getTopCard,
13
+ getZoneSize,
14
+ isCardInZone,
15
+ mill,
16
+ moveCard,
17
+ peek,
18
+ removeCard,
19
+ reveal,
20
+ search,
21
+ shuffle,
22
+ } from "./zone-operations";
23
+ export {
24
+ createPlayerZones,
25
+ getCardZone,
26
+ moveCardInState,
27
+ } from "./zone-state-helpers";
28
+ export { filterZoneByVisibility } from "./zone-visibility";