macroforge 0.1.33 → 0.1.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -339
- package/index.d.ts +955 -21
- package/index.js +52 -52
- package/js/serde/index.d.ts +242 -16
- package/js/serde/index.mjs +12 -4
- package/js/serde/index.ts +264 -21
- package/js/traits/index.d.ts +227 -2
- package/js/traits/index.ts +229 -11
- package/js/utils/index.d.ts +41 -0
- package/js/utils/index.ts +42 -0
- package/package.json +7 -7
package/js/serde/index.ts
CHANGED
|
@@ -1,21 +1,104 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* and
|
|
2
|
+
* # Macroforge Serde Module
|
|
3
|
+
*
|
|
4
|
+
* This module provides runtime helpers for the `Serialize` and `Deserialize` macros.
|
|
5
|
+
* It handles complex serialization scenarios including:
|
|
6
|
+
*
|
|
7
|
+
* - **Cycle Detection**: Objects are assigned unique `__id` values during serialization.
|
|
8
|
+
* When the same object is encountered again, a `{ "__ref": id }` marker is emitted
|
|
9
|
+
* instead of re-serializing the object.
|
|
10
|
+
*
|
|
11
|
+
* - **Forward References**: During deserialization, references to objects that haven't
|
|
12
|
+
* been created yet are tracked as `PendingRef` markers. After all objects are
|
|
13
|
+
* instantiated, `applyPatches()` resolves these references.
|
|
14
|
+
*
|
|
15
|
+
* - **Validation Errors**: The `DeserializeError` class collects structured field-level
|
|
16
|
+
* errors that can be displayed to users.
|
|
17
|
+
*
|
|
18
|
+
* ## Serialization Flow
|
|
19
|
+
*
|
|
20
|
+
* ```typescript
|
|
21
|
+
* // Generated code creates a context
|
|
22
|
+
* const ctx = SerializeContext.create();
|
|
23
|
+
*
|
|
24
|
+
* // Each object gets registered with a unique ID
|
|
25
|
+
* const id = ctx.register(obj);
|
|
26
|
+
*
|
|
27
|
+
* // Before serializing, check if already seen
|
|
28
|
+
* const existingId = ctx.getId(obj);
|
|
29
|
+
* if (existingId !== undefined) {
|
|
30
|
+
* return { __ref: existingId }; // Return reference marker
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* ## Deserialization Flow
|
|
35
|
+
*
|
|
36
|
+
* ```typescript
|
|
37
|
+
* // Generated code creates a context
|
|
38
|
+
* const ctx = DeserializeContext.create();
|
|
39
|
+
*
|
|
40
|
+
* // Register objects as they're created
|
|
41
|
+
* ctx.register(id, instance);
|
|
42
|
+
*
|
|
43
|
+
* // References are resolved immediately if available, or deferred
|
|
44
|
+
* const value = ctx.getOrDefer(refId);
|
|
45
|
+
*
|
|
46
|
+
* // After all objects are created, resolve pending references
|
|
47
|
+
* ctx.applyPatches();
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @module macroforge/serde
|
|
5
51
|
*/
|
|
6
52
|
|
|
7
53
|
// ============================================================================
|
|
8
54
|
// Serialization Context
|
|
9
55
|
// ============================================================================
|
|
10
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Context for tracking objects during serialization.
|
|
59
|
+
*
|
|
60
|
+
* The context assigns unique IDs to objects as they're serialized,
|
|
61
|
+
* enabling cycle detection. When an object is encountered that has
|
|
62
|
+
* already been serialized, a `{ "__ref": id }` marker is emitted instead.
|
|
63
|
+
*/
|
|
11
64
|
export interface SerializeContext {
|
|
12
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Gets the ID for an already-registered object.
|
|
67
|
+
* @param obj - The object to look up
|
|
68
|
+
* @returns The object's ID, or `undefined` if not yet registered
|
|
69
|
+
*/
|
|
13
70
|
getId(obj: object): number | undefined;
|
|
14
|
-
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Registers an object and assigns it a unique ID.
|
|
74
|
+
* @param obj - The object to register
|
|
75
|
+
* @returns The newly assigned ID
|
|
76
|
+
*/
|
|
15
77
|
register(obj: object): number;
|
|
16
78
|
}
|
|
17
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Factory functions for creating serialization contexts.
|
|
82
|
+
*/
|
|
18
83
|
export namespace SerializeContext {
|
|
84
|
+
/**
|
|
85
|
+
* Creates a new serialization context.
|
|
86
|
+
*
|
|
87
|
+
* The context uses a `WeakMap` to track objects, so objects can be
|
|
88
|
+
* garbage collected if no other references exist.
|
|
89
|
+
*
|
|
90
|
+
* @returns A new `SerializeContext` instance
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```typescript
|
|
94
|
+
* const ctx = SerializeContext.create();
|
|
95
|
+
* const id = ctx.register(myObject);
|
|
96
|
+
* // Later...
|
|
97
|
+
* if (ctx.getId(myObject) !== undefined) {
|
|
98
|
+
* // Object was already serialized, emit reference
|
|
99
|
+
* }
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
19
102
|
export function create(): SerializeContext {
|
|
20
103
|
const ids = new WeakMap<object, number>();
|
|
21
104
|
let nextId = 0;
|
|
@@ -34,25 +117,107 @@ export namespace SerializeContext {
|
|
|
34
117
|
// Deserialization Context
|
|
35
118
|
// ============================================================================
|
|
36
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Context for tracking objects and resolving references during deserialization.
|
|
122
|
+
*
|
|
123
|
+
* The context maintains a registry of objects by their `__id` values and
|
|
124
|
+
* collects "patches" for forward references that need to be resolved after
|
|
125
|
+
* all objects are created.
|
|
126
|
+
*
|
|
127
|
+
* ## Forward Reference Resolution
|
|
128
|
+
*
|
|
129
|
+
* When deserializing circular or forward references:
|
|
130
|
+
*
|
|
131
|
+
* 1. Object A references Object B (which hasn't been created yet)
|
|
132
|
+
* 2. A `PendingRef` marker is stored temporarily
|
|
133
|
+
* 3. Object B is created and registered with its ID
|
|
134
|
+
* 4. `applyPatches()` resolves A's reference to point to B
|
|
135
|
+
*/
|
|
37
136
|
export interface DeserializeContext {
|
|
38
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* Registers an object with a known ID.
|
|
139
|
+
* @param id - The object's ID from the `__id` field
|
|
140
|
+
* @param instance - The deserialized object instance
|
|
141
|
+
*/
|
|
39
142
|
register(id: number, instance: any): void;
|
|
40
|
-
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Gets an object by ID, or returns a `PendingRef` if not yet available.
|
|
146
|
+
* @param refId - The ID from the `__ref` field
|
|
147
|
+
* @returns The object if already registered, or a `PendingRef` marker
|
|
148
|
+
*/
|
|
41
149
|
getOrDefer(refId: number): any;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Assigns a value to a property, deferring if it's a `PendingRef`.
|
|
153
|
+
* If the value is a `PendingRef`, the assignment is recorded as a patch
|
|
154
|
+
* to be applied later.
|
|
155
|
+
* @param target - The object to assign to
|
|
156
|
+
* @param prop - The property name or index
|
|
157
|
+
* @param value - The value to assign (may be a `PendingRef`)
|
|
158
|
+
*/
|
|
159
|
+
assignOrDefer(target: any, prop: string | number, value: any): void;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Manually adds a patch for later resolution.
|
|
163
|
+
* @param target - The object containing the reference
|
|
164
|
+
* @param prop - The property name or index
|
|
165
|
+
* @param refId - The ID of the referenced object
|
|
166
|
+
*/
|
|
167
|
+
addPatch(target: any, prop: string | number, refId: number): void;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Tracks an object for optional freezing after deserialization.
|
|
171
|
+
* @param obj - The object to track
|
|
172
|
+
*/
|
|
45
173
|
trackForFreeze(obj: object): void;
|
|
46
|
-
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Applies all deferred patches, resolving forward references.
|
|
177
|
+
* Call this after all objects have been created.
|
|
178
|
+
* @throws Error if any referenced ID is not in the registry
|
|
179
|
+
*/
|
|
47
180
|
applyPatches(): void;
|
|
48
|
-
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Freezes all tracked objects for immutability.
|
|
184
|
+
* Call this after `applyPatches()` if immutable objects are desired.
|
|
185
|
+
*/
|
|
49
186
|
freezeAll(): void;
|
|
50
187
|
}
|
|
51
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Factory functions for creating deserialization contexts.
|
|
191
|
+
*/
|
|
52
192
|
export namespace DeserializeContext {
|
|
193
|
+
/**
|
|
194
|
+
* Creates a new deserialization context.
|
|
195
|
+
*
|
|
196
|
+
* The context maintains:
|
|
197
|
+
* - A registry mapping IDs to deserialized objects
|
|
198
|
+
* - A list of patches for forward references
|
|
199
|
+
* - A list of objects to freeze (if immutability is enabled)
|
|
200
|
+
*
|
|
201
|
+
* @returns A new `DeserializeContext` instance
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* ```typescript
|
|
205
|
+
* const ctx = DeserializeContext.create();
|
|
206
|
+
*
|
|
207
|
+
* // Register objects as they're created
|
|
208
|
+
* ctx.register(1, user);
|
|
209
|
+
* ctx.register(2, friend);
|
|
210
|
+
*
|
|
211
|
+
* // Resolve forward references
|
|
212
|
+
* ctx.applyPatches();
|
|
213
|
+
*
|
|
214
|
+
* // Optionally freeze for immutability
|
|
215
|
+
* ctx.freezeAll();
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
53
218
|
export function create(): DeserializeContext {
|
|
54
219
|
const registry = new Map<number, any>();
|
|
55
|
-
const patches: Array<{
|
|
220
|
+
const patches: Array<{ target: any; prop: string | number; refId: number }> = [];
|
|
56
221
|
const toFreeze: object[] = [];
|
|
57
222
|
|
|
58
223
|
return {
|
|
@@ -67,8 +232,17 @@ export namespace DeserializeContext {
|
|
|
67
232
|
return PendingRef.create(refId);
|
|
68
233
|
},
|
|
69
234
|
|
|
70
|
-
|
|
71
|
-
|
|
235
|
+
assignOrDefer: (target, prop, value) => {
|
|
236
|
+
if (PendingRef.is(value)) {
|
|
237
|
+
target[prop] = null;
|
|
238
|
+
patches.push({ target, prop, refId: value.id });
|
|
239
|
+
} else {
|
|
240
|
+
target[prop] = value;
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
addPatch: (target, prop, refId) => {
|
|
245
|
+
patches.push({ target, prop, refId });
|
|
72
246
|
},
|
|
73
247
|
|
|
74
248
|
trackForFreeze: (obj) => {
|
|
@@ -76,11 +250,11 @@ export namespace DeserializeContext {
|
|
|
76
250
|
},
|
|
77
251
|
|
|
78
252
|
applyPatches: () => {
|
|
79
|
-
for (const {
|
|
253
|
+
for (const { target, prop, refId } of patches) {
|
|
80
254
|
if (!registry.has(refId)) {
|
|
81
255
|
throw new Error(`Unresolved reference: __ref ${refId}`);
|
|
82
256
|
}
|
|
83
|
-
|
|
257
|
+
target[prop] = registry.get(refId);
|
|
84
258
|
}
|
|
85
259
|
},
|
|
86
260
|
|
|
@@ -97,17 +271,38 @@ export namespace DeserializeContext {
|
|
|
97
271
|
// Pending Reference Marker
|
|
98
272
|
// ============================================================================
|
|
99
273
|
|
|
100
|
-
/**
|
|
274
|
+
/**
|
|
275
|
+
* Marker interface for forward references that need patching.
|
|
276
|
+
*
|
|
277
|
+
* When deserializing a `{ "__ref": id }` marker for an object that hasn't
|
|
278
|
+
* been created yet, a `PendingRef` is stored temporarily. After all objects
|
|
279
|
+
* are created, `DeserializeContext.applyPatches()` resolves these markers.
|
|
280
|
+
*/
|
|
101
281
|
export interface PendingRef {
|
|
282
|
+
/** Discriminant field to identify pending references */
|
|
102
283
|
readonly __pendingRef: true;
|
|
284
|
+
/** The ID of the referenced object */
|
|
103
285
|
readonly id: number;
|
|
104
286
|
}
|
|
105
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Factory and type guard functions for `PendingRef`.
|
|
290
|
+
*/
|
|
106
291
|
export namespace PendingRef {
|
|
292
|
+
/**
|
|
293
|
+
* Creates a new pending reference marker.
|
|
294
|
+
* @param id - The ID of the referenced object
|
|
295
|
+
* @returns A `PendingRef` marker
|
|
296
|
+
*/
|
|
107
297
|
export function create(id: number): PendingRef {
|
|
108
298
|
return { __pendingRef: true, id };
|
|
109
299
|
}
|
|
110
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Type guard to check if a value is a `PendingRef`.
|
|
303
|
+
* @param value - The value to check
|
|
304
|
+
* @returns `true` if the value is a `PendingRef`
|
|
305
|
+
*/
|
|
111
306
|
export function is(value: any): value is PendingRef {
|
|
112
307
|
return (
|
|
113
308
|
value !== null &&
|
|
@@ -122,8 +317,15 @@ export namespace PendingRef {
|
|
|
122
317
|
// Options for fromStringifiedJSON
|
|
123
318
|
// ============================================================================
|
|
124
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Options for configuring deserialization behavior.
|
|
322
|
+
*/
|
|
125
323
|
export interface DeserializeOptions {
|
|
126
|
-
/**
|
|
324
|
+
/**
|
|
325
|
+
* If `true`, all deserialized objects are frozen after patching.
|
|
326
|
+
* This provides immutability guarantees but prevents modification.
|
|
327
|
+
* @default false
|
|
328
|
+
*/
|
|
127
329
|
freeze?: boolean;
|
|
128
330
|
}
|
|
129
331
|
|
|
@@ -131,16 +333,57 @@ export interface DeserializeOptions {
|
|
|
131
333
|
// Structured Error for Deserialization
|
|
132
334
|
// ============================================================================
|
|
133
335
|
|
|
134
|
-
/**
|
|
336
|
+
/**
|
|
337
|
+
* Structured error for a single field validation failure.
|
|
338
|
+
*
|
|
339
|
+
* Used by the `Deserialize` macro to collect validation errors
|
|
340
|
+
* in a format suitable for display to users.
|
|
341
|
+
*/
|
|
135
342
|
export interface FieldError {
|
|
343
|
+
/**
|
|
344
|
+
* The field path that failed validation.
|
|
345
|
+
* For nested fields, uses dot notation (e.g., `"address.street"`).
|
|
346
|
+
*/
|
|
136
347
|
field: string;
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Human-readable error message describing the validation failure.
|
|
351
|
+
* @example "must be a valid email"
|
|
352
|
+
* @example "must have at least 3 characters"
|
|
353
|
+
*/
|
|
137
354
|
message: string;
|
|
138
355
|
}
|
|
139
356
|
|
|
140
|
-
/**
|
|
357
|
+
/**
|
|
358
|
+
* Error class that carries structured field validation errors.
|
|
359
|
+
*
|
|
360
|
+
* Thrown by deserialization methods when validation fails. Contains
|
|
361
|
+
* an array of `FieldError` objects that can be displayed to users
|
|
362
|
+
* or used for form validation feedback.
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* ```typescript
|
|
366
|
+
* try {
|
|
367
|
+
* const user = User.fromStringifiedJSON(json);
|
|
368
|
+
* } catch (e) {
|
|
369
|
+
* if (e instanceof DeserializeError) {
|
|
370
|
+
* for (const { field, message } of e.errors) {
|
|
371
|
+
* console.error(`${field}: ${message}`);
|
|
372
|
+
* }
|
|
373
|
+
* }
|
|
374
|
+
* }
|
|
375
|
+
* ```
|
|
376
|
+
*/
|
|
141
377
|
export class DeserializeError extends Error {
|
|
378
|
+
/**
|
|
379
|
+
* Array of field-level validation errors.
|
|
380
|
+
*/
|
|
142
381
|
public readonly errors: FieldError[];
|
|
143
382
|
|
|
383
|
+
/**
|
|
384
|
+
* Creates a new deserialization error.
|
|
385
|
+
* @param errors - Array of field validation errors
|
|
386
|
+
*/
|
|
144
387
|
constructor(errors: FieldError[]) {
|
|
145
388
|
const message = errors.map((e) => `${e.field}: ${e.message}`).join("; ");
|
|
146
389
|
super(message);
|
package/js/traits/index.d.ts
CHANGED
|
@@ -1,33 +1,258 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* # Macroforge Traits Module
|
|
3
|
+
*
|
|
4
|
+
* This module defines TypeScript interfaces that correspond to Rust-like traits.
|
|
5
|
+
* These interfaces describe the shape of the methods generated by Macroforge's
|
|
6
|
+
* derive macros for interfaces, enums, and type aliases.
|
|
7
|
+
*
|
|
8
|
+
* For **classes**, the generated methods are instance methods (e.g., `user.clone()`).
|
|
9
|
+
* For **interfaces/enums/type aliases**, the generated methods are namespace
|
|
10
|
+
* functions that take the value as the first parameter (e.g., `User.clone(user)`).
|
|
11
|
+
*
|
|
12
|
+
* ## Usage
|
|
13
|
+
*
|
|
14
|
+
* These traits are primarily used for type checking generated code:
|
|
15
|
+
*
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import type { Clone, Debug } from "macroforge/traits";
|
|
18
|
+
*
|
|
19
|
+
* // Type-check that a namespace implements Clone
|
|
20
|
+
* const UserNs: Clone<User> = User;
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @module macroforge/traits
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Trait for types that can be deep-copied.
|
|
27
|
+
*
|
|
28
|
+
* Analogous to Rust's `Clone` trait. The generated `clone` method creates
|
|
29
|
+
* an independent copy of the value.
|
|
30
|
+
*
|
|
31
|
+
* @template T - The type being cloned
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* // For interfaces/type aliases, use as namespace:
|
|
36
|
+
* const cloned = UserNs.clone(original);
|
|
37
|
+
*
|
|
38
|
+
* // For classes, methods are on the instance:
|
|
39
|
+
* const cloned = original.clone();
|
|
40
|
+
* ```
|
|
4
41
|
*/
|
|
5
42
|
export interface Clone<T> {
|
|
43
|
+
/**
|
|
44
|
+
* Creates a deep copy of the value.
|
|
45
|
+
* @param self - The value to clone
|
|
46
|
+
* @returns A new independent copy of the value
|
|
47
|
+
*/
|
|
6
48
|
readonly clone: (self: T) => T;
|
|
7
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Trait for types with a human-readable string representation.
|
|
52
|
+
*
|
|
53
|
+
* Analogous to Rust's `Debug` trait. The generated `toString` method
|
|
54
|
+
* produces a string like `"TypeName { field1: value1, field2: value2 }"`.
|
|
55
|
+
*
|
|
56
|
+
* @template T - The type being formatted
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* console.log(User.toString(user));
|
|
61
|
+
* // Output: "User { id: 1, name: Alice }"
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
8
64
|
export interface Debug<T> {
|
|
65
|
+
/**
|
|
66
|
+
* Returns a debug string representation of the value.
|
|
67
|
+
* @param self - The value to format
|
|
68
|
+
* @returns A human-readable string for debugging
|
|
69
|
+
*/
|
|
9
70
|
readonly toString: (self: T) => string;
|
|
10
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Trait for types with a default value.
|
|
74
|
+
*
|
|
75
|
+
* Analogous to Rust's `Default` trait. The generated `defaultValue` method
|
|
76
|
+
* creates an instance with all fields set to their default values.
|
|
77
|
+
*
|
|
78
|
+
* @template T - The type being constructed
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* const user = User.defaultValue();
|
|
83
|
+
* // Creates: { id: 0, name: "", active: false }
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
11
86
|
export interface Default<T> {
|
|
87
|
+
/**
|
|
88
|
+
* Creates a new instance with default values for all fields.
|
|
89
|
+
* @returns A new instance with default field values
|
|
90
|
+
*/
|
|
12
91
|
readonly defaultValue: () => T;
|
|
13
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Trait for types that support equality comparison.
|
|
95
|
+
*
|
|
96
|
+
* Analogous to Rust's `PartialEq` trait. The generated `equals` method
|
|
97
|
+
* performs structural equality comparison on all non-skipped fields.
|
|
98
|
+
*
|
|
99
|
+
* @template T - The type being compared
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* if (User.equals(user1, user2)) {
|
|
104
|
+
* console.log("Users are equal");
|
|
105
|
+
* }
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
14
108
|
export interface PartialEq<T> {
|
|
109
|
+
/**
|
|
110
|
+
* Compares two values for equality.
|
|
111
|
+
* @param self - The first value
|
|
112
|
+
* @param other - The second value (accepts unknown for flexibility)
|
|
113
|
+
* @returns `true` if the values are equal, `false` otherwise
|
|
114
|
+
*/
|
|
15
115
|
readonly equals: (self: T, other: unknown) => boolean;
|
|
16
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Trait for types that can produce a hash code.
|
|
119
|
+
*
|
|
120
|
+
* Analogous to Rust's `Hash` trait. The generated `hashCode` method
|
|
121
|
+
* computes a 32-bit integer hash suitable for use in hash-based collections.
|
|
122
|
+
*
|
|
123
|
+
* **Hash Contract**: Objects that are equal (via `PartialEq`) must produce
|
|
124
|
+
* the same hash code.
|
|
125
|
+
*
|
|
126
|
+
* @template T - The type being hashed
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* const hash = User.hashCode(user);
|
|
131
|
+
* // Use in Map: map.set(hash, user);
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
17
134
|
export interface Hash<T> {
|
|
135
|
+
/**
|
|
136
|
+
* Computes a hash code for the value.
|
|
137
|
+
* @param self - The value to hash
|
|
138
|
+
* @returns A 32-bit integer hash code
|
|
139
|
+
*/
|
|
18
140
|
readonly hashCode: (self: T) => number;
|
|
19
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Trait for types with partial ordering.
|
|
144
|
+
*
|
|
145
|
+
* Analogous to Rust's `PartialOrd` trait. The generated `compareTo` method
|
|
146
|
+
* returns a number for comparable values, or `null` for incomparable values.
|
|
147
|
+
*
|
|
148
|
+
* @template T - The type being compared
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```typescript
|
|
152
|
+
* const cmp = User.compareTo(user1, user2);
|
|
153
|
+
* if (cmp !== null) {
|
|
154
|
+
* if (cmp < 0) console.log("user1 < user2");
|
|
155
|
+
* else if (cmp > 0) console.log("user1 > user2");
|
|
156
|
+
* else console.log("user1 == user2");
|
|
157
|
+
* } else {
|
|
158
|
+
* console.log("Incomparable");
|
|
159
|
+
* }
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
20
162
|
export interface PartialOrd<T> {
|
|
163
|
+
/**
|
|
164
|
+
* Compares two values for ordering.
|
|
165
|
+
* @param self - The first value
|
|
166
|
+
* @param other - The second value
|
|
167
|
+
* @returns `-1` if self < other, `0` if equal, `1` if self > other, or `null` if incomparable
|
|
168
|
+
*/
|
|
21
169
|
readonly compareTo: (self: T, other: unknown) => number | null;
|
|
22
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Trait for types with total ordering.
|
|
173
|
+
*
|
|
174
|
+
* Analogous to Rust's `Ord` trait. The generated `compareTo` method
|
|
175
|
+
* always returns a number (never null) - all values are comparable.
|
|
176
|
+
*
|
|
177
|
+
* @template T - The type being compared
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript
|
|
181
|
+
* // Sort an array using Ord
|
|
182
|
+
* users.sort((a, b) => User.compareTo(a, b));
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
23
185
|
export interface Ord<T> {
|
|
186
|
+
/**
|
|
187
|
+
* Compares two values for ordering (total order).
|
|
188
|
+
* @param self - The first value
|
|
189
|
+
* @param other - The second value
|
|
190
|
+
* @returns `-1` if self < other, `0` if equal, `1` if self > other
|
|
191
|
+
*/
|
|
24
192
|
readonly compareTo: (self: T, other: T) => number;
|
|
25
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Trait for types that can be serialized to JSON.
|
|
196
|
+
*
|
|
197
|
+
* Analogous to Rust's serde `Serialize` trait. The generated methods
|
|
198
|
+
* convert objects to JSON with cycle detection via `__id`/`__ref` markers.
|
|
199
|
+
*
|
|
200
|
+
* @template T - The type being serialized
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```typescript
|
|
204
|
+
* const json = User.toStringifiedJSON(user);
|
|
205
|
+
* // => '{"__type":"User","__id":1,"name":"Alice"}'
|
|
206
|
+
*
|
|
207
|
+
* const obj = User.toObject(user);
|
|
208
|
+
* // => { __type: "User", __id: 1, name: "Alice" }
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
26
211
|
export interface Serialize<T> {
|
|
212
|
+
/**
|
|
213
|
+
* Serializes the value to a JSON string.
|
|
214
|
+
* @param self - The value to serialize
|
|
215
|
+
* @returns JSON string with `__type` and `__id` markers
|
|
216
|
+
*/
|
|
27
217
|
readonly toStringifiedJSON: (self: T) => string;
|
|
218
|
+
/**
|
|
219
|
+
* Converts the value to a plain JavaScript object.
|
|
220
|
+
* @param self - The value to convert
|
|
221
|
+
* @returns Plain object suitable for JSON.stringify
|
|
222
|
+
*/
|
|
28
223
|
readonly toObject: (self: T) => Record<string, unknown>;
|
|
29
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Trait for types that can be deserialized from JSON.
|
|
227
|
+
*
|
|
228
|
+
* Analogous to Rust's serde `Deserialize` trait. The generated methods
|
|
229
|
+
* parse JSON, resolve `__ref` markers, and validate field values.
|
|
230
|
+
*
|
|
231
|
+
* @template T - The type being deserialized
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```typescript
|
|
235
|
+
* import { Result } from "macroforge/utils";
|
|
236
|
+
*
|
|
237
|
+
* const result = User.fromStringifiedJSON(json);
|
|
238
|
+
* if (Result.isOk(result)) {
|
|
239
|
+
* const user = result.value;
|
|
240
|
+
* } else {
|
|
241
|
+
* console.error(result.error); // Validation errors
|
|
242
|
+
* }
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
30
245
|
export interface Deserialize<T> {
|
|
246
|
+
/**
|
|
247
|
+
* Deserializes a value from a JSON string.
|
|
248
|
+
* @param json - JSON string to parse
|
|
249
|
+
* @returns The deserialized value (may throw on invalid input)
|
|
250
|
+
*/
|
|
31
251
|
readonly fromStringifiedJSON: (json: string) => T;
|
|
252
|
+
/**
|
|
253
|
+
* Deserializes a value from a plain JavaScript object.
|
|
254
|
+
* @param obj - Object to deserialize from
|
|
255
|
+
* @returns The deserialized value (may throw on invalid input)
|
|
256
|
+
*/
|
|
32
257
|
readonly fromObject: (obj: unknown) => T;
|
|
33
258
|
}
|