@cleverbrush/mapper 0.0.0-beta-20260410073748
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 +340 -0
- package/dist/MappingRegistry.d.ts +258 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# @cleverbrush/mapper
|
|
2
|
+
|
|
3
|
+
[](https://github.com/cleverbrush/framework/actions/workflows/ci.yml)
|
|
4
|
+
[](../../LICENSE)
|
|
5
|
+
<!-- coverage-badge-start -->
|
|
6
|
+

|
|
7
|
+
<!-- coverage-badge-end -->
|
|
8
|
+
|
|
9
|
+
A type-safe, declarative object mapper for converting objects between different `@cleverbrush/schema` representations. Uses PropertyDescriptors as pointers to properties (similar to expressions in C# .NET) and enforces **compile-time completeness** — TypeScript will produce an error if any target property is not mapped, auto-mapped, or explicitly ignored.
|
|
10
|
+
|
|
11
|
+
## Why @cleverbrush/mapper?
|
|
12
|
+
|
|
13
|
+
**The problem:** Converting between different object shapes — API responses to domain models, domain models to DTOs, database rows to view models — is tedious and error-prone. You write manual mapping functions full of `destination.x = source.y` assignments. Add a new property to a schema and nothing tells you the mapper is incomplete. The bug shows up at runtime, not at compile time.
|
|
14
|
+
|
|
15
|
+
**The solution:** `@cleverbrush/mapper` uses **PropertyDescriptor-based selectors** (similar to C# expression trees) for type-safe property mapping. The TypeScript compiler enforces that **every target property is mapped** — unmapped properties cause a compile-time error. You literally cannot forget a field.
|
|
16
|
+
|
|
17
|
+
**What makes it different:**
|
|
18
|
+
|
|
19
|
+
- **Compile-time completeness** — unmapped properties are a TypeScript error, not a runtime surprise
|
|
20
|
+
- **Type-safe selectors** — `.for((t) => t.name).from((s) => s.name)` — fully checked at compile time, not string-based
|
|
21
|
+
- **Auto-mapping** — properties with the same name and compatible type are mapped automatically; you only configure what differs
|
|
22
|
+
- **Immutable registry** — `configure()` returns a new registry; safe to share and extend
|
|
23
|
+
- **No decorators or classes** — works with plain objects and schemas
|
|
24
|
+
|
|
25
|
+
| Feature | @cleverbrush/mapper | AutoMapper-ts | class-transformer | morphism |
|
|
26
|
+
| --- | --- | --- | --- | --- |
|
|
27
|
+
| Compile-time completeness | ✓ | ✗ | ✗ | ✗ |
|
|
28
|
+
| Type-safe selectors | ✓ | ✗ | ✗ | ✗ |
|
|
29
|
+
| No decorators required | ✓ | ✗ | ✗ | ✓ |
|
|
30
|
+
| Works without classes | ✓ | ✗ | ✗ | ✓ |
|
|
31
|
+
| Auto-mapping | ✓ | ✓ | ✗ | ✗ |
|
|
32
|
+
| Immutable registry | ✓ | ✗ | ✗ | ✗ |
|
|
33
|
+
| Nested schema support | ✓ | ~ | ~ | ✗ |
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install @cleverbrush/mapper
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Peer dependency:** `@cleverbrush/schema`
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { object, string, number } from '@cleverbrush/schema';
|
|
47
|
+
import { mapper } from '@cleverbrush/mapper';
|
|
48
|
+
|
|
49
|
+
// Define source and target schemas
|
|
50
|
+
const ApiUser = object({
|
|
51
|
+
first_name: string(),
|
|
52
|
+
last_name: string(),
|
|
53
|
+
birth_year: number()
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const DomainUser = object({
|
|
57
|
+
fullName: string(),
|
|
58
|
+
age: number()
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Configure the mapping — returns a new (immutable) registry
|
|
62
|
+
const registry = mapper().configure(
|
|
63
|
+
ApiUser,
|
|
64
|
+
DomainUser,
|
|
65
|
+
(m) =>
|
|
66
|
+
m
|
|
67
|
+
.for((t) => t.fullName)
|
|
68
|
+
.compute((src) => src.first_name + ' ' + src.last_name)
|
|
69
|
+
.for((t) => t.age)
|
|
70
|
+
.compute((src) => new Date().getFullYear() - src.birth_year)
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// Get the mapper function and use it
|
|
74
|
+
const mapFn = registry.getMapper(ApiUser, DomainUser);
|
|
75
|
+
|
|
76
|
+
const dto = await mapFn({
|
|
77
|
+
first_name: 'Jane',
|
|
78
|
+
last_name: 'Doe',
|
|
79
|
+
birth_year: 1995
|
|
80
|
+
});
|
|
81
|
+
// { fullName: 'Jane Doe', age: <current year - 1995> }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## How It Works — Step by Step
|
|
85
|
+
|
|
86
|
+
1. **Define schemas** — use `@cleverbrush/schema` to define source and target shapes
|
|
87
|
+
2. **Configure mappings** — use `.for()` to select a target property, then `.from()`, `.compute()`, or `.ignore()` to define how it's populated
|
|
88
|
+
3. **Auto-mapping fills the gaps** — properties with the same name and compatible type are mapped automatically
|
|
89
|
+
4. **Get a mapper function** — `registry.getMapper(from, to)` returns an async function that transforms objects
|
|
90
|
+
5. **TypeScript enforces completeness** — if any target property is unmapped, you get a compile-time error
|
|
91
|
+
|
|
92
|
+
## Compile-Time Safety
|
|
93
|
+
|
|
94
|
+
The mapper enforces multiple layers of compile-time safety:
|
|
95
|
+
|
|
96
|
+
### Unmapped properties
|
|
97
|
+
|
|
98
|
+
Every target property must be either mapped, auto-mapped, or explicitly ignored. If you forget to map a property, TypeScript will produce a compile-time error on the `configure` callback return:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
mapper().configure(
|
|
102
|
+
UserSchema,
|
|
103
|
+
UserDtoSchema,
|
|
104
|
+
(m) =>
|
|
105
|
+
m
|
|
106
|
+
.for((t) => t.name)
|
|
107
|
+
.from((f) => f.name)
|
|
108
|
+
.for((t) => t.cityName)
|
|
109
|
+
.from((f) => f.address.city)
|
|
110
|
+
// TS Error: Type 'Mapper<..., "fullAddress", ...>' is not assignable to
|
|
111
|
+
// type 'Mapper<..., never, ...>'.
|
|
112
|
+
// Types of property '[SYMBOL_UNMAPPED]' are incompatible.
|
|
113
|
+
// Type '"fullAddress"' is not assignable to type 'never'.
|
|
114
|
+
);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The error message shows the names of the unmapped properties directly in the type mismatch.
|
|
118
|
+
|
|
119
|
+
### Type-incompatible `from`
|
|
120
|
+
|
|
121
|
+
`from` only shows source properties whose `InferType` is assignable to the target property's type. If you select an incompatible property (e.g., mapping a `number` to a `string`), TypeScript produces a compile-time error:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// Trying to map a string target from a number source
|
|
125
|
+
m.for((t) => t.cityName).from((f) => f.houseNr)
|
|
126
|
+
// TS Error: source property type is not assignable to target property type
|
|
127
|
+
// Use .compute() instead to transform the value
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Unregistered nested mappings
|
|
131
|
+
|
|
132
|
+
When `from` maps between two `ObjectSchemaBuilder` properties, a mapping for that schema pair must be registered in the registry first. Otherwise, TypeScript produces a compile-time error:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
const PersonSchema = object({ name: string(), address: AddressSchema });
|
|
136
|
+
const PersonDtoSchema = object({ name: string(), address: AddressDtoSchema });
|
|
137
|
+
|
|
138
|
+
// Error — AddressSchema→AddressDtoSchema is not registered
|
|
139
|
+
mapper().configure(
|
|
140
|
+
PersonSchema,
|
|
141
|
+
PersonDtoSchema,
|
|
142
|
+
(m) =>
|
|
143
|
+
m
|
|
144
|
+
.for((t) => t.name)
|
|
145
|
+
.from((f) => f.name)
|
|
146
|
+
.for((t) => t.address)
|
|
147
|
+
.from((f) => f.address) // TS Error: Register a mapping for the
|
|
148
|
+
// source→target schema pair first
|
|
149
|
+
);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Auto-Mapping
|
|
153
|
+
|
|
154
|
+
Properties that can be automatically determined don't need explicit mapping configuration. Auto-mapping activates in two scenarios:
|
|
155
|
+
|
|
156
|
+
### Same-name, same-type primitives
|
|
157
|
+
|
|
158
|
+
When the source and target schemas have a property with the **same name** and **compatible `InferType`**, it is auto-mapped automatically:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
const Source = object({
|
|
162
|
+
id: string(),
|
|
163
|
+
name: string(),
|
|
164
|
+
email: string(),
|
|
165
|
+
age: number()
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const Target = object({
|
|
169
|
+
id: string(), // same name + type → auto-mapped
|
|
170
|
+
name: string(), // same name + type → auto-mapped
|
|
171
|
+
email: string(), // same name + type → auto-mapped
|
|
172
|
+
ageGroup: string() // different name → must be configured
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const registry = mapper().configure(Source, Target, (m) =>
|
|
176
|
+
m
|
|
177
|
+
.for((t) => t.ageGroup)
|
|
178
|
+
.compute((src) => src.age < 18 ? 'minor' : 'adult')
|
|
179
|
+
// id, name, email are auto-mapped — no configuration needed!
|
|
180
|
+
);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Nested ObjectSchemaBuilder properties
|
|
184
|
+
|
|
185
|
+
When both the source and target have a same-name property that is an `ObjectSchemaBuilder`, and a mapping for that schema pair has been previously registered, the nested property is auto-mapped using the registered mapper:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
const AddressSchema = object({ city: string(), houseNr: number() });
|
|
189
|
+
const AddressDtoSchema = object({ city: string() });
|
|
190
|
+
|
|
191
|
+
const PersonSchema = object({ name: string(), address: AddressSchema });
|
|
192
|
+
const PersonDtoSchema = object({ name: string(), address: AddressDtoSchema });
|
|
193
|
+
|
|
194
|
+
const registry = mapper()
|
|
195
|
+
// Register Address mapping first
|
|
196
|
+
.configure(AddressSchema, AddressDtoSchema, (m) =>
|
|
197
|
+
m.for((t) => t.city).from((f) => f.city)
|
|
198
|
+
)
|
|
199
|
+
// address is auto-mapped using the registered AddressSchema→AddressDtoSchema mapper
|
|
200
|
+
.configure(PersonSchema, PersonDtoSchema, (m) =>
|
|
201
|
+
m.for((t) => t.name).from((f) => f.name)
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const mapFn = registry.getMapper(PersonSchema, PersonDtoSchema);
|
|
205
|
+
const result = await mapFn({
|
|
206
|
+
name: 'Alice',
|
|
207
|
+
address: { city: 'Berlin', houseNr: 10 }
|
|
208
|
+
});
|
|
209
|
+
// result: { name: 'Alice', address: { city: 'Berlin' } }
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Ordering matters:** nested mappings must be registered before the parent mapping. Explicit mappings via `compute` or `ignore` take priority over auto-mapping.
|
|
213
|
+
|
|
214
|
+
## Mapping Strategies
|
|
215
|
+
|
|
216
|
+
| Strategy | Usage | Purpose |
|
|
217
|
+
| --- | --- | --- |
|
|
218
|
+
| `.from(selector)` | `.for(t => t.x).from(s => s.y)` | Copy from a source property (supports nested paths) |
|
|
219
|
+
| `.compute(fn)` | `.for(t => t.x).compute(s => s.a + s.b)` | Compute from a sync or async function |
|
|
220
|
+
| `.ignore()` | `.for(t => t.x).ignore()` | Exclude a target property |
|
|
221
|
+
| _(auto-mapped)_ | _(no configuration needed)_ | Same-name, compatible-type primitives or registered nested schemas |
|
|
222
|
+
|
|
223
|
+
Every non-auto-mappable target property must be either mapped or explicitly ignored. Unmapped properties cause:
|
|
224
|
+
|
|
225
|
+
- A **compile-time TypeScript type error** — a type-assignability mismatch that includes the unmapped property names in the type parameters
|
|
226
|
+
- A **runtime `MapperConfigurationError`** if type checks are bypassed
|
|
227
|
+
|
|
228
|
+
## API
|
|
229
|
+
|
|
230
|
+
### `mapper()`
|
|
231
|
+
|
|
232
|
+
A convenience factory function that creates a new `MappingRegistry`:
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
const registry = mapper()
|
|
236
|
+
.configure(A, B, (m) => ...)
|
|
237
|
+
.configure(C, D, (m) => ...);
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### `registry.configure(fromSchema, toSchema, fn)`
|
|
241
|
+
|
|
242
|
+
Defines a mapping between two schemas and returns a new immutable registry containing the mapping. The callback `fn` receives a fresh `Mapper` and must return it after configuring all non-auto-mappable property mappings.
|
|
243
|
+
|
|
244
|
+
Throws if schemas are invalid, the mapping is a duplicate, or if unmapped properties remain that cannot be auto-mapped.
|
|
245
|
+
|
|
246
|
+
### `registry.getMapper(fromSchema, toSchema)`
|
|
247
|
+
|
|
248
|
+
Retrieves a previously registered mapper function. Throws if no mapper has been registered for the given schema pair.
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
const mapFn = registry.getMapper(ApiUser, DomainUser);
|
|
252
|
+
const result = await mapFn(sourceObject);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### `Mapper`
|
|
256
|
+
|
|
257
|
+
A fluent builder for configuring how each target property is populated:
|
|
258
|
+
|
|
259
|
+
- **`.for(selector)`** — selects a target property to configure
|
|
260
|
+
- **`.from(selector)`** — maps from a source property (types must be compatible)
|
|
261
|
+
- **`.compute(fn)`** — computes the value from the entire source object (sync or async)
|
|
262
|
+
- **`.ignore()`** — explicitly excludes the property
|
|
263
|
+
- **`.getMapper()`** — returns the mapping function (only available when all properties are mapped)
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
const mapper = new Mapper(SourceSchema, TargetSchema);
|
|
267
|
+
const mapFn = mapper
|
|
268
|
+
.for((t) => t.fullName)
|
|
269
|
+
.compute((src) => `${src.firstName} ${src.lastName}`)
|
|
270
|
+
.for((t) => t.age)
|
|
271
|
+
.from((src) => src.years)
|
|
272
|
+
.getMapper();
|
|
273
|
+
|
|
274
|
+
const result = await mapFn(sourceObject);
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Real-World Example
|
|
278
|
+
|
|
279
|
+
A complete example mapping API responses through multiple layers:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { object, string, number } from '@cleverbrush/schema';
|
|
283
|
+
import { mapper } from '@cleverbrush/mapper';
|
|
284
|
+
|
|
285
|
+
// API response shape
|
|
286
|
+
const ApiOrderResponse = object({
|
|
287
|
+
order_id: string(),
|
|
288
|
+
customer_name: string(),
|
|
289
|
+
total_cents: number(),
|
|
290
|
+
status_code: number()
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Domain model
|
|
294
|
+
const Order = object({
|
|
295
|
+
id: string(),
|
|
296
|
+
customer: string(),
|
|
297
|
+
totalPrice: string(),
|
|
298
|
+
status: string()
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const registry = mapper().configure(
|
|
302
|
+
ApiOrderResponse,
|
|
303
|
+
Order,
|
|
304
|
+
(m) =>
|
|
305
|
+
m
|
|
306
|
+
.for((t) => t.id)
|
|
307
|
+
.from((s) => s.order_id)
|
|
308
|
+
.for((t) => t.customer)
|
|
309
|
+
.from((s) => s.customer_name)
|
|
310
|
+
.for((t) => t.totalPrice)
|
|
311
|
+
.compute((s) => `$${(s.total_cents / 100).toFixed(2)}`)
|
|
312
|
+
.for((t) => t.status)
|
|
313
|
+
.compute((s) => {
|
|
314
|
+
const statuses: Record<number, string> = {
|
|
315
|
+
0: 'pending', 1: 'confirmed', 2: 'shipped', 3: 'delivered'
|
|
316
|
+
};
|
|
317
|
+
return statuses[s.status_code] ?? 'unknown';
|
|
318
|
+
})
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
const mapOrder = registry.getMapper(ApiOrderResponse, Order);
|
|
322
|
+
const order = await mapOrder({
|
|
323
|
+
order_id: 'ORD-123',
|
|
324
|
+
customer_name: 'Alice Smith',
|
|
325
|
+
total_cents: 4999,
|
|
326
|
+
status_code: 2
|
|
327
|
+
});
|
|
328
|
+
// { id: 'ORD-123', customer: 'Alice Smith', totalPrice: '$49.99', status: 'shipped' }
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Code Quality
|
|
332
|
+
|
|
333
|
+
- **Linting:** [Biome](https://biomejs.dev/) — enforced on every PR via CI
|
|
334
|
+
- **Type checking:** TypeScript strict mode — all type selectors and mapping configurations are validated at compile time
|
|
335
|
+
- **Unit tests:** [Vitest](https://vitest.dev/) — runtime tests + type-level tests (`expectTypeOf`) covering auto-mapping, computed fields, nested schemas, and compile-time completeness errors
|
|
336
|
+
- **CI:** Every pull request must pass lint + build + test before merge — see [`.github/workflows/ci.yml`](../../.github/workflows/ci.yml)
|
|
337
|
+
|
|
338
|
+
## License
|
|
339
|
+
|
|
340
|
+
BSD-3-Clause
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { ArraySchemaBuilder, type InferType, ObjectSchemaBuilder, type PropertyDescriptor, type PropertyDescriptorTree, type SchemaBuilder, SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR } from '@cleverbrush/schema';
|
|
2
|
+
/**
|
|
3
|
+
* Internal symbol used to brand target property descriptors with their key.
|
|
4
|
+
* Using a symbol instead of a string property keeps it out of IntelliSense.
|
|
5
|
+
*/
|
|
6
|
+
declare const SYMBOL_TARGET_PROPERTY_KEY: unique symbol;
|
|
7
|
+
/**
|
|
8
|
+
* Internal symbol used as a phantom property key on Mapper to track
|
|
9
|
+
* unmapped target properties. Using a symbol keeps it out of IntelliSense.
|
|
10
|
+
*/
|
|
11
|
+
declare const SYMBOL_UNMAPPED: unique symbol;
|
|
12
|
+
/**
|
|
13
|
+
* Extracts the properties record from an ObjectSchemaBuilder.
|
|
14
|
+
*/
|
|
15
|
+
type ExtractSchemaProperties<T> = T extends ObjectSchemaBuilder<infer TProperties, any, any> ? TProperties : never;
|
|
16
|
+
/**
|
|
17
|
+
* Gets all top-level property key names of an ObjectSchemaBuilder.
|
|
18
|
+
*/
|
|
19
|
+
type SchemaKeys<T extends ObjectSchemaBuilder<any, any, any>> = keyof ExtractSchemaProperties<T> & string;
|
|
20
|
+
/**
|
|
21
|
+
* Branded phantom type that tags a property descriptor with its key name.
|
|
22
|
+
* This allows TypeScript to infer which property was selected in the
|
|
23
|
+
* `for` callback, enabling compile-time tracking of mapped vs unmapped
|
|
24
|
+
* properties.
|
|
25
|
+
*/
|
|
26
|
+
type TargetPropertyKey<K extends string> = {
|
|
27
|
+
readonly [SYMBOL_TARGET_PROPERTY_KEY]: K;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Creates a tree of selectable target properties, filtered to only show
|
|
31
|
+
* properties whose keys are in `TAllowedKeys`. Each property is branded
|
|
32
|
+
* with `TargetPropertyKey<K>` so the key can be inferred from the return
|
|
33
|
+
* type of the selector callback.
|
|
34
|
+
*/
|
|
35
|
+
type TargetPropertyTree<TSchema extends ObjectSchemaBuilder<any, any, any>, TAllowedKeys extends string> = {
|
|
36
|
+
[K in SchemaKeys<TSchema> & TAllowedKeys]: TargetPropertyKey<K> & PropertyDescriptor<TSchema, ExtractSchemaProperties<TSchema>[K], any>;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Infers the TypeScript type of a specific property in an ObjectSchemaBuilder
|
|
40
|
+
* by its key name.
|
|
41
|
+
*/
|
|
42
|
+
type SchemaPropertyInferredType<TSchema extends ObjectSchemaBuilder<any, any, any>, K extends string> = K extends keyof ExtractSchemaProperties<TSchema> ? InferType<ExtractSchemaProperties<TSchema>[K]> : never;
|
|
43
|
+
/**
|
|
44
|
+
* Extracts the schema (SchemaBuilder) of a specific property in an
|
|
45
|
+
* ObjectSchemaBuilder by its key name.
|
|
46
|
+
*/
|
|
47
|
+
type TargetPropertySchema<TSchema extends ObjectSchemaBuilder<any, any, any>, K extends string> = K extends keyof ExtractSchemaProperties<TSchema> ? ExtractSchemaProperties<TSchema>[K] : never;
|
|
48
|
+
/**
|
|
49
|
+
* Extracts the element schema from an ArraySchemaBuilder.
|
|
50
|
+
*/
|
|
51
|
+
type ExtractArrayElementSchema<T> = T extends ArraySchemaBuilder<infer TElementSchema, any, any> ? TElementSchema : never;
|
|
52
|
+
/**
|
|
53
|
+
* Extracts the property schema from a PropertyDescriptor's `getSchema()`
|
|
54
|
+
* return type. Used by {@link IsFromCompatible} to recover the source
|
|
55
|
+
* property's schema from the inferred `TReturn` of the `from()` selector.
|
|
56
|
+
*/
|
|
57
|
+
type ExtractPropertySchema<T> = T extends {
|
|
58
|
+
[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR]: {
|
|
59
|
+
getSchema(): infer TSchema;
|
|
60
|
+
};
|
|
61
|
+
} ? TSchema : never;
|
|
62
|
+
/**
|
|
63
|
+
* Determines whether a property needs an explicit mapping configuration.
|
|
64
|
+
*
|
|
65
|
+
* - Both are ObjectSchemaBuilder with registered mapping → `false` (auto-mappable)
|
|
66
|
+
* - Both are ObjectSchemaBuilder without registered mapping → `true`
|
|
67
|
+
* - Either is ObjectSchemaBuilder but the other is not → `true`
|
|
68
|
+
* - Both are ArraySchemaBuilder with element schemas that have registered mapping → `false`
|
|
69
|
+
* - Both are ArraySchemaBuilder with same InferType → `false`
|
|
70
|
+
* - Array vs non-array → `true`
|
|
71
|
+
* - Neither is ObjectSchemaBuilder + InferType<source> extends InferType<target>
|
|
72
|
+
* → `false` (same-name, compatible primitive types → auto-mappable)
|
|
73
|
+
* - Neither is ObjectSchemaBuilder + incompatible InferType → `true`
|
|
74
|
+
*/
|
|
75
|
+
type NeedsMapping<TSourcePropSchema, TTargetPropSchema, TRegistered> = TSourcePropSchema extends ArraySchemaBuilder<any, any, any> ? TTargetPropSchema extends ArraySchemaBuilder<any, any, any> ? NeedsMapping<ExtractArrayElementSchema<TSourcePropSchema>, ExtractArrayElementSchema<TTargetPropSchema>, TRegistered> : true : TTargetPropSchema extends ArraySchemaBuilder<any, any, any> ? true : TSourcePropSchema extends ObjectSchemaBuilder<any, any, any> ? TTargetPropSchema extends ObjectSchemaBuilder<any, any, any> ? [TSourcePropSchema, TTargetPropSchema] extends TRegistered ? false : InferType<TSourcePropSchema> extends InferType<TTargetPropSchema> ? InferType<TTargetPropSchema> extends InferType<TSourcePropSchema> ? false : true : true : true : TTargetPropSchema extends ObjectSchemaBuilder<any, any, any> ? true : InferType<TSourcePropSchema> extends InferType<TTargetPropSchema> ? false : true;
|
|
76
|
+
/**
|
|
77
|
+
* From all keys of the target schema, filter down to only those that
|
|
78
|
+
* require explicit mapping (i.e. `NeedsMapping` is `true`).
|
|
79
|
+
* Keys where `NeedsMapping` is `false` can be auto-mapped via the registry.
|
|
80
|
+
*/
|
|
81
|
+
type KeysNeedingMapping<TFromSchema extends ObjectSchemaBuilder<any, any, any>, TToSchema extends ObjectSchemaBuilder<any, any, any>, TRegistered> = {
|
|
82
|
+
[K in SchemaKeys<TToSchema>]: K extends SchemaKeys<TFromSchema> ? NeedsMapping<ExtractSchemaProperties<TFromSchema>[K], ExtractSchemaProperties<TToSchema>[K], TRegistered> extends true ? K : never : K;
|
|
83
|
+
}[SchemaKeys<TToSchema>];
|
|
84
|
+
/**
|
|
85
|
+
* Checks whether two property schemas are compatible, considering
|
|
86
|
+
* registered mappings. Mirrors {@link NeedsMapping}'s structure:
|
|
87
|
+
* receives schemas as direct type parameters, uses
|
|
88
|
+
* {@link ExtractArrayElementSchema} for arrays, and checks registration
|
|
89
|
+
* via tuple-extends-union (not inline `infer`).
|
|
90
|
+
*/
|
|
91
|
+
type CheckSchemaCompatible<TSourceSchema, TTargetSchema, TRegistered> = [
|
|
92
|
+
InferType<TSourceSchema>
|
|
93
|
+
] extends [InferType<TTargetSchema>] ? true : TSourceSchema extends ArraySchemaBuilder<any, any, any> ? TTargetSchema extends ArraySchemaBuilder<any, any, any> ? CheckSchemaCompatible<ExtractArrayElementSchema<TSourceSchema>, ExtractArrayElementSchema<TTargetSchema>, TRegistered> : false : TSourceSchema extends ObjectSchemaBuilder<any, any, any> ? TTargetSchema extends ObjectSchemaBuilder<any, any, any> ? [TSourceSchema, TTargetSchema] extends TRegistered ? true : false : false : false;
|
|
94
|
+
/**
|
|
95
|
+
* Checks whether the source property selected by `from()` is compatible
|
|
96
|
+
* with the target property, evaluated *after* `TReturn` has been inferred.
|
|
97
|
+
*
|
|
98
|
+
* This decouples inference from validation: the `TReturn` constraint uses
|
|
99
|
+
* `any` for setValue/getValue value types so TypeScript always infers
|
|
100
|
+
* successfully, and this type performs the actual compatibility check
|
|
101
|
+
* in the `_args` conditional spread.
|
|
102
|
+
*/
|
|
103
|
+
type IsFromCompatible<TReturn, TToSchema extends ObjectSchemaBuilder<any, any, any>, TKey extends string, TRegistered> = [ExtractPropertySchema<TReturn>] extends [never] ? false : CheckSchemaCompatible<ExtractPropertySchema<TReturn>, TargetPropertySchema<TToSchema, TKey>, TRegistered>;
|
|
104
|
+
export type SchemaToSchemaMapperResult<TFromSchema extends ObjectSchemaBuilder<any, any, any>, TToSchema extends ObjectSchemaBuilder<any, any, any>> = (from: InferType<TFromSchema>) => Promise<InferType<TToSchema>>;
|
|
105
|
+
export declare class MapperConfigurationError extends Error {
|
|
106
|
+
constructor(messageOrUnmappedProperties: string | string[]);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Intermediate builder returned by `for()`. Provides three strategies
|
|
110
|
+
* to configure how the selected target property is populated:
|
|
111
|
+
* - `from()` — copy from a source property
|
|
112
|
+
* - `compute()` — compute from the entire source object
|
|
113
|
+
* - `ignore()` — explicitly skip the property
|
|
114
|
+
*/
|
|
115
|
+
export declare class PropertyMappingBuilder<TFromSchema extends ObjectSchemaBuilder<any, any, any>, TToSchema extends ObjectSchemaBuilder<any, any, any>, TKey extends string, TUnmapped extends string, TRegistered = never> {
|
|
116
|
+
private readonly _mapper;
|
|
117
|
+
private readonly _targetKey;
|
|
118
|
+
/** @internal */
|
|
119
|
+
constructor(mapper: Mapper<TFromSchema, TToSchema, any, TRegistered>, targetKey: TKey);
|
|
120
|
+
/**
|
|
121
|
+
* Maps the target property from a source property. The selector
|
|
122
|
+
* receives the source schema's PropertyDescriptorTree and supports
|
|
123
|
+
* nested paths (e.g. `(s) => s.address.city`).
|
|
124
|
+
*
|
|
125
|
+
* Type compatibility is enforced: only source properties whose
|
|
126
|
+
* inferred type is assignable to the target property type will
|
|
127
|
+
* appear in the selector callback.
|
|
128
|
+
*
|
|
129
|
+
* Under `strictFunctionTypes`, the `setValue` and `getValue` constraints
|
|
130
|
+
* provide bidirectional type checking:
|
|
131
|
+
* - `setValue` contravariance rejects source schemas with extra properties
|
|
132
|
+
* - `getValue` covariance rejects source schemas with missing properties
|
|
133
|
+
* - registered mappings widen the constraint via intersection
|
|
134
|
+
*/
|
|
135
|
+
from<TReturn extends {
|
|
136
|
+
[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR]: {
|
|
137
|
+
getSchema(): SchemaBuilder<any, any, any>;
|
|
138
|
+
setValue: (obj: any, value: any) => any;
|
|
139
|
+
getValue: (obj: any) => {
|
|
140
|
+
value?: any;
|
|
141
|
+
success: boolean;
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
}>(selector: (tree: PropertyDescriptorTree<TFromSchema, TFromSchema, SchemaPropertyInferredType<TToSchema, TKey>>) => TReturn, ..._args: [TReturn] extends [never] ? [
|
|
145
|
+
error: `Property '${TKey}': source property type is not assignable to the target property type. Use compute() instead.`
|
|
146
|
+
] : IsFromCompatible<TReturn, TToSchema, TKey, TRegistered> extends true ? [] : [
|
|
147
|
+
error: `Property '${TKey}': source property type is not assignable to the target property type. Use compute() instead.`
|
|
148
|
+
]): Mapper<TFromSchema, TToSchema, Exclude<TUnmapped, TKey>, TRegistered>;
|
|
149
|
+
/**
|
|
150
|
+
* Computes the target property value from the entire source object.
|
|
151
|
+
* Supports both sync and async functions.
|
|
152
|
+
*/
|
|
153
|
+
compute(fn: ((obj: InferType<TFromSchema>) => SchemaPropertyInferredType<TToSchema, TKey>) | ((obj: InferType<TFromSchema>) => Promise<SchemaPropertyInferredType<TToSchema, TKey>>)): Mapper<TFromSchema, TToSchema, Exclude<TUnmapped, TKey>, TRegistered>;
|
|
154
|
+
/**
|
|
155
|
+
* Explicitly excludes the target property from mapping.
|
|
156
|
+
* The property will not appear in the output object.
|
|
157
|
+
*/
|
|
158
|
+
ignore(): Mapper<TFromSchema, TToSchema, Exclude<TUnmapped, TKey>, TRegistered>;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* A fluent builder for configuring how each target property is populated
|
|
162
|
+
* from a source schema. Uses PropertyDescriptors as pointers to properties
|
|
163
|
+
* (similar to expressions in C# .NET).
|
|
164
|
+
*
|
|
165
|
+
* The `TUnmapped` type parameter tracks which target properties have not
|
|
166
|
+
* yet been mapped or ignored. `getMapper()` is only callable (without
|
|
167
|
+
* arguments) when `TUnmapped` is `never` — i.e. all properties have been
|
|
168
|
+
* accounted for. If any property is missing, TypeScript will produce a
|
|
169
|
+
* compile-time type error (a type-assignability mismatch that includes
|
|
170
|
+
* the unmapped property names in its type parameters).
|
|
171
|
+
*
|
|
172
|
+
* @typeParam TFromSchema - source ObjectSchemaBuilder
|
|
173
|
+
* @typeParam TToSchema - target ObjectSchemaBuilder
|
|
174
|
+
* @typeParam TUnmapped - union of target property key names not yet mapped
|
|
175
|
+
*/
|
|
176
|
+
export declare class Mapper<TFromSchema extends ObjectSchemaBuilder<any, any, any>, TToSchema extends ObjectSchemaBuilder<any, any, any>, TUnmapped extends string = SchemaKeys<TToSchema>, TRegistered = never> {
|
|
177
|
+
/** Phantom property for structural type-checking of TUnmapped. */
|
|
178
|
+
readonly [SYMBOL_UNMAPPED]: TUnmapped;
|
|
179
|
+
private readonly _fromSchema;
|
|
180
|
+
private readonly _toSchema;
|
|
181
|
+
private readonly _registry;
|
|
182
|
+
private readonly _mappings;
|
|
183
|
+
/**
|
|
184
|
+
* Creates a new instance of the Mapper class.
|
|
185
|
+
* @param fromSchema - `object` schema to map from
|
|
186
|
+
* @param toSchema - `object` schema to map to
|
|
187
|
+
* @param registry - optional MappingRegistry to auto-register the mapper
|
|
188
|
+
*/
|
|
189
|
+
constructor(fromSchema: TFromSchema, toSchema: TToSchema, registry?: MappingRegistry<any>);
|
|
190
|
+
/**
|
|
191
|
+
* Selects a target property to configure. The selector callback
|
|
192
|
+
* receives a tree of all target properties. Navigate by
|
|
193
|
+
* property name: `(t) => t.cityName`.
|
|
194
|
+
*
|
|
195
|
+
* Auto-mappable properties (same name and compatible type, or
|
|
196
|
+
* ObjectSchemaBuilder with a registered mapping) are also available
|
|
197
|
+
* for explicit override.
|
|
198
|
+
*/
|
|
199
|
+
for<TKey extends SchemaKeys<TToSchema>>(selector: (tree: TargetPropertyTree<TToSchema, SchemaKeys<TToSchema>>) => TargetPropertyKey<TKey>): PropertyMappingBuilder<TFromSchema, TToSchema, TKey, TUnmapped, TRegistered>;
|
|
200
|
+
/**
|
|
201
|
+
* Returns the configured async mapping function.
|
|
202
|
+
*
|
|
203
|
+
* **Compile-time safety:** This method is only callable without
|
|
204
|
+
* arguments when all target properties have been mapped or explicitly
|
|
205
|
+
* ignored. If any property is unmapped, TypeScript will require
|
|
206
|
+
* a string argument describing the unmapped properties, producing
|
|
207
|
+
* a clear compile-time error.
|
|
208
|
+
*
|
|
209
|
+
* **Runtime safety:** Even if TypeScript checks are bypassed (e.g.
|
|
210
|
+
* via `as any`), a `MapperConfigurationError` is thrown at runtime
|
|
211
|
+
* listing the unmapped properties.
|
|
212
|
+
*/
|
|
213
|
+
getMapper(..._args: [TUnmapped] extends [never] ? [] : [error: `Unmapped properties: ${TUnmapped}`]): SchemaToSchemaMapperResult<TFromSchema, TToSchema>;
|
|
214
|
+
}
|
|
215
|
+
export declare class MappingRegistry<TRegistered = never> {
|
|
216
|
+
#private;
|
|
217
|
+
protected readonly _mappers: Map<ObjectSchemaBuilder<any, any, any>, Map<ObjectSchemaBuilder<any, any, any>, SchemaToSchemaMapperResult<any, any>>>;
|
|
218
|
+
/**
|
|
219
|
+
* Defines a mapping between two schemas and returns a new immutable
|
|
220
|
+
* registry containing the mapping. The callback `fn` receives a fresh
|
|
221
|
+
* `Mapper` and must return it after configuring property mappings.
|
|
222
|
+
* The mapper is automatically finalized and registered. Properties
|
|
223
|
+
* not explicitly mapped or ignored may be auto-mapped if a matching
|
|
224
|
+
* nested mapping is already registered in the registry.
|
|
225
|
+
*
|
|
226
|
+
* @param fromSchema - source ObjectSchemaBuilder
|
|
227
|
+
* @param toSchema - target ObjectSchemaBuilder
|
|
228
|
+
* @param fn - callback that configures property mappings on the mapper
|
|
229
|
+
* @returns a new MappingRegistry containing all previous mappings plus
|
|
230
|
+
* the newly configured one
|
|
231
|
+
* @throws if schemas are invalid, mapping is duplicate, or unmapped
|
|
232
|
+
* properties remain that cannot be auto-mapped
|
|
233
|
+
*/
|
|
234
|
+
configure<TFromSchema extends ObjectSchemaBuilder<any, any, any>, TToSchema extends ObjectSchemaBuilder<any, any, any>>(fromSchema: TFromSchema, toSchema: TToSchema, fn: (mapper: Mapper<TFromSchema, TToSchema, KeysNeedingMapping<TFromSchema, TToSchema, TRegistered>, TRegistered>) => Mapper<TFromSchema, TToSchema, never, TRegistered>): MappingRegistry<TRegistered | [TFromSchema, TToSchema]>;
|
|
235
|
+
/**
|
|
236
|
+
* Gets a mapper function that will map from the fromSchema to the toSchema.
|
|
237
|
+
* Throws an error if no mapper is found for the given schemas pair.
|
|
238
|
+
* @param fromSchema a schema to map from
|
|
239
|
+
* @param toSchema a schema to map to
|
|
240
|
+
* @returns a function that will take a value of the fromSchema type as an argument and
|
|
241
|
+
* return a promise resolving to a value of the toSchema type
|
|
242
|
+
*/
|
|
243
|
+
getMapper<TFromSchema extends ObjectSchemaBuilder<any, any, any>, TToSchema extends ObjectSchemaBuilder<any, any, any>>(fromSchema: TFromSchema, toSchema: TToSchema): SchemaToSchemaMapperResult<TFromSchema, TToSchema>;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Creates a new empty {@link MappingRegistry}.
|
|
247
|
+
*
|
|
248
|
+
* This is a convenience factory function — an alternative to
|
|
249
|
+
* `new MappingRegistry()` that reads better in a fluent chain:
|
|
250
|
+
*
|
|
251
|
+
* ```ts
|
|
252
|
+
* const registry = mapper()
|
|
253
|
+
* .configure(A, B, m => ...)
|
|
254
|
+
* .configure(C, D, m => ...);
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
export declare function mapper(): MappingRegistry;
|
|
258
|
+
export {};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{ArraySchemaBuilder as u,ObjectSchemaBuilder as i,SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR as g}from"@cleverbrush/schema";var j=Symbol("targetPropertyKey");var l=class extends Error{constructor(e){super(typeof e=="string"?e:`Mapper configuration error: the following target properties are not mapped and not ignored: ${e.join(", ")}`),this.name="MapperConfigurationError"}};function b(m,e,r){if(m instanceof i&&e instanceof i){if(r){let o=r._mappers.get(m)?.get(e);if(o)return o}let a=Object.keys(m.introspect().properties||{}).sort(),s=Object.keys(e.introspect().properties||{}).sort();return a.length===s.length&&a.every((t,o)=>t===s[o])?async t=>t:null}if(m instanceof u&&e instanceof u){let a=m.introspect().elementSchema,s=e.introspect().elementSchema;if(a&&s){let t=b(a,s,r);if(t)return async o=>{if(o!=null)return Array.isArray(o)?Promise.all(o.map(t)):o}}return null}if(!(m instanceof i)&&!(e instanceof i)&&!(m instanceof u)&&!(e instanceof u)){let a=typeof m.introspect=="function"?m.introspect():void 0,s=typeof e.introspect=="function"?e.introspect():void 0;return!a||!s||a.type!==s.type?null:async t=>t}return null}var x=class{_mapper;_targetKey;constructor(e,r){this._mapper=e,this._targetKey=r}from(e,...r){let a=i.getPropertiesFor(this._mapper._fromSchema),t=e(a)[g],o=t.getSchema(),y=(this._mapper._toSchema.introspect().properties||{})[this._targetKey];if(o instanceof i&&y instanceof i&&this._mapper._registry){let T=this._mapper._registry._mappers.get(o)?.get(y);if(T)return this._mapper._mappings.set(this._targetKey,{type:"auto",sourceDescriptorInner:t,autoMapper:T}),this._mapper}if(o instanceof u&&y instanceof u){let h=o.introspect().elementSchema,T=y.introspect().elementSchema;if(h&&T){let p=b(h,T,this._mapper._registry);if(p)return this._mapper._mappings.set(this._targetKey,{type:"autoArray",sourceDescriptorInner:t,elementMapper:p}),this._mapper}}return this._mapper._mappings.set(this._targetKey,{type:"prop",sourceDescriptorInner:t}),this._mapper}compute(e){return this._mapper._mappings.set(this._targetKey,{type:"custom",fn:e}),this._mapper}ignore(){return this._mapper._mappings.set(this._targetKey,{type:"ignore"}),this._mapper}},M=class{_fromSchema;_toSchema;_registry;_mappings=new Map;constructor(e,r,a){this._fromSchema=e,this._toSchema=r,this._registry=a}for(e){let r,a=new Proxy({},{get(o,d){return typeof d=="string"&&(r=d),{[j]:d}}});if(e(a),!r)throw new Error("for selector must access a property on the target tree");let s=this._toSchema.introspect();if(!(s.properties?Object.keys(s.properties):[]).includes(r))throw new l(`Property "${r}" does not exist in the target schema`);return new x(this,r)}getMapper(...e){let r=this._toSchema.introspect(),a=r.properties?Object.keys(r.properties):[];if(this._registry){let h=this._fromSchema.introspect().properties||{},T=r.properties||{},p=i.getPropertiesFor(this._fromSchema);for(let c of a){if(this._mappings.has(c))continue;let S=h[c],n=T[c];if(!S||!n)continue;let f=p[c];if(f?.[g]){if(S instanceof i&&n instanceof i){let _=this._registry._mappers.get(S)?.get(n);if(_){this._mappings.set(c,{type:"auto",sourceDescriptorInner:f[g],autoMapper:_});continue}let P=Object.keys(S.introspect().properties||{}).sort(),B=Object.keys(n.introspect().properties||{}).sort();P.length===B.length&&P.every((O,E)=>O===B[E])&&this._mappings.set(c,{type:"prop",sourceDescriptorInner:f[g]});continue}if(!(S instanceof i)&&!(n instanceof i)&&!(S instanceof u)&&!(n instanceof u)){this._mappings.set(c,{type:"prop",sourceDescriptorInner:f[g]});continue}if(S instanceof u&&n instanceof u){let K=S.introspect().elementSchema,_=n.introspect().elementSchema;if(K&&_){let P=b(K,_,this._registry);P&&this._mappings.set(c,{type:"autoArray",sourceDescriptorInner:f[g],elementMapper:P})}}}}}let s=a.filter(y=>!this._mappings.has(y));if(s.length>0)throw new l(s);let t=i.getPropertiesFor(this._toSchema),o=new Map(this._mappings),d=async y=>{let h={};for(let[T,p]of o){if(p.type==="ignore")continue;let c;if(p.type==="prop"){let n=p.sourceDescriptorInner.getValue(y);if(n.success)c=n.value;else continue}else if(p.type==="custom")c=await p.fn(y);else if(p.type==="auto"){let n=p.sourceDescriptorInner.getValue(y);if(n.success&&n.value!==void 0)c=await p.autoMapper(n.value);else continue}else if(p.type==="autoArray"){let n=p.sourceDescriptorInner.getValue(y);if(n.success&&n.value!=null){if(!Array.isArray(n.value))throw new l(`Expected array for property "${T}" but got ${typeof n.value}`);c=await Promise.all(n.value.map(p.elementMapper))}else continue}let S=t[T];S?.[g]?S[g].setValue(h,c,{createMissingStructure:!0}):h[T]=c}return h};return this._registry&&this._registry._mappers.get(this._fromSchema)?.set(this._toSchema,d),d}},R=class m{_mappers=new Map;#e(e,r){return!(!e||!r||!(e instanceof i)||!(r instanceof i))}configure(e,r,a){if(!this.#e(e,r))throw new Error("Both fromSchema and toSchema must be instances of ObjectSchemaBuilder");if(this._mappers.get(e)?.has(r))throw new Error("Duplicate mapping: a mapping for this schemas pair is already registered");let t=new m;for(let[y,h]of this._mappers)t._mappers.set(y,new Map(h));t._mappers.has(e)||t._mappers.set(e,new Map);let o=new M(e,r,t);return a(o).getMapper(),t}getMapper(e,r){if(!this.#e(e,r))throw new Error("Both fromSchema and toSchema must be instances of ObjectSchemaBuilder");let a=this._mappers.get(e);if(!a)throw new Error("No mapper found for the given schemas pair");let s=a.get(r);if(!s)throw new Error("No mapper found for the given schemas pair");return s}};function I(){return new R}export{M as Mapper,l as MapperConfigurationError,R as MappingRegistry,x as PropertyMappingBuilder,I as mapper};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/MappingRegistry.ts"],"sourcesContent":["import {\n ArraySchemaBuilder,\n type InferType,\n ObjectSchemaBuilder,\n type PropertyDescriptor,\n type PropertyDescriptorTree,\n type SchemaBuilder,\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n} from '@cleverbrush/schema';\n\n/**\n * Internal symbol used to brand target property descriptors with their key.\n * Using a symbol instead of a string property keeps it out of IntelliSense.\n */\nconst SYMBOL_TARGET_PROPERTY_KEY: unique symbol = Symbol('targetPropertyKey');\n\n/**\n * Internal symbol used as a phantom property key on Mapper to track\n * unmapped target properties. Using a symbol keeps it out of IntelliSense.\n */\nconst SYMBOL_UNMAPPED: unique symbol = Symbol('unmapped');\n\n// ── Helper Types ──────────────────────────────────────────────────────\n\n/**\n * Extracts the properties record from an ObjectSchemaBuilder.\n */\ntype ExtractSchemaProperties<T> =\n T extends ObjectSchemaBuilder<infer TProperties, any, any>\n ? TProperties\n : never;\n\n/**\n * Gets all top-level property key names of an ObjectSchemaBuilder.\n */\ntype SchemaKeys<T extends ObjectSchemaBuilder<any, any, any>> =\n keyof ExtractSchemaProperties<T> & string;\n\n/**\n * Branded phantom type that tags a property descriptor with its key name.\n * This allows TypeScript to infer which property was selected in the\n * `for` callback, enabling compile-time tracking of mapped vs unmapped\n * properties.\n */\ntype TargetPropertyKey<K extends string> = {\n readonly [SYMBOL_TARGET_PROPERTY_KEY]: K;\n};\n\n/**\n * Creates a tree of selectable target properties, filtered to only show\n * properties whose keys are in `TAllowedKeys`. Each property is branded\n * with `TargetPropertyKey<K>` so the key can be inferred from the return\n * type of the selector callback.\n */\ntype TargetPropertyTree<\n TSchema extends ObjectSchemaBuilder<any, any, any>,\n TAllowedKeys extends string\n> = {\n [K in SchemaKeys<TSchema> & TAllowedKeys]: TargetPropertyKey<K> &\n PropertyDescriptor<TSchema, ExtractSchemaProperties<TSchema>[K], any>;\n};\n\n/**\n * Infers the TypeScript type of a specific property in an ObjectSchemaBuilder\n * by its key name.\n */\ntype SchemaPropertyInferredType<\n TSchema extends ObjectSchemaBuilder<any, any, any>,\n K extends string\n> = K extends keyof ExtractSchemaProperties<TSchema>\n ? InferType<ExtractSchemaProperties<TSchema>[K]>\n : never;\n\n/**\n * Extracts the schema (SchemaBuilder) of a specific property in an\n * ObjectSchemaBuilder by its key name.\n */\ntype TargetPropertySchema<\n TSchema extends ObjectSchemaBuilder<any, any, any>,\n K extends string\n> = K extends keyof ExtractSchemaProperties<TSchema>\n ? ExtractSchemaProperties<TSchema>[K]\n : never;\n\n/**\n * Extracts the element schema from an ArraySchemaBuilder.\n */\ntype ExtractArrayElementSchema<T> =\n T extends ArraySchemaBuilder<infer TElementSchema, any, any>\n ? TElementSchema\n : never;\n\n/**\n * Extracts the property schema from a PropertyDescriptor's `getSchema()`\n * return type. Used by {@link IsFromCompatible} to recover the source\n * property's schema from the inferred `TReturn` of the `from()` selector.\n */\ntype ExtractPropertySchema<T> = T extends {\n [SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR]: {\n getSchema(): infer TSchema;\n };\n}\n ? TSchema\n : never;\n\n/**\n * Determines whether a property needs an explicit mapping configuration.\n *\n * - Both are ObjectSchemaBuilder with registered mapping → `false` (auto-mappable)\n * - Both are ObjectSchemaBuilder without registered mapping → `true`\n * - Either is ObjectSchemaBuilder but the other is not → `true`\n * - Both are ArraySchemaBuilder with element schemas that have registered mapping → `false`\n * - Both are ArraySchemaBuilder with same InferType → `false`\n * - Array vs non-array → `true`\n * - Neither is ObjectSchemaBuilder + InferType<source> extends InferType<target>\n * → `false` (same-name, compatible primitive types → auto-mappable)\n * - Neither is ObjectSchemaBuilder + incompatible InferType → `true`\n */\ntype NeedsMapping<TSourcePropSchema, TTargetPropSchema, TRegistered> =\n TSourcePropSchema extends ArraySchemaBuilder<any, any, any>\n ? TTargetPropSchema extends ArraySchemaBuilder<any, any, any>\n ? NeedsMapping<\n ExtractArrayElementSchema<TSourcePropSchema>,\n ExtractArrayElementSchema<TTargetPropSchema>,\n TRegistered\n >\n : true\n : TTargetPropSchema extends ArraySchemaBuilder<any, any, any>\n ? true\n : TSourcePropSchema extends ObjectSchemaBuilder<any, any, any>\n ? TTargetPropSchema extends ObjectSchemaBuilder<any, any, any>\n ? [TSourcePropSchema, TTargetPropSchema] extends TRegistered\n ? false\n : InferType<TSourcePropSchema> extends InferType<TTargetPropSchema>\n ? InferType<TTargetPropSchema> extends InferType<TSourcePropSchema>\n ? false\n : true\n : true\n : true\n : TTargetPropSchema extends ObjectSchemaBuilder<any, any, any>\n ? true\n : InferType<TSourcePropSchema> extends InferType<TTargetPropSchema>\n ? false\n : true;\n\n/**\n * From all keys of the target schema, filter down to only those that\n * require explicit mapping (i.e. `NeedsMapping` is `true`).\n * Keys where `NeedsMapping` is `false` can be auto-mapped via the registry.\n */\ntype KeysNeedingMapping<\n TFromSchema extends ObjectSchemaBuilder<any, any, any>,\n TToSchema extends ObjectSchemaBuilder<any, any, any>,\n TRegistered\n> = {\n [K in SchemaKeys<TToSchema>]: K extends SchemaKeys<TFromSchema>\n ? NeedsMapping<\n ExtractSchemaProperties<TFromSchema>[K],\n ExtractSchemaProperties<TToSchema>[K],\n TRegistered\n > extends true\n ? K\n : never\n : K;\n}[SchemaKeys<TToSchema>];\n\n/**\n * Checks whether two property schemas are compatible, considering\n * registered mappings. Mirrors {@link NeedsMapping}'s structure:\n * receives schemas as direct type parameters, uses\n * {@link ExtractArrayElementSchema} for arrays, and checks registration\n * via tuple-extends-union (not inline `infer`).\n */\ntype CheckSchemaCompatible<TSourceSchema, TTargetSchema, TRegistered> =\n // Direct InferType match → compatible\n [InferType<TSourceSchema>] extends [InferType<TTargetSchema>]\n ? true\n : // Both arrays → recurse into element schemas\n TSourceSchema extends ArraySchemaBuilder<any, any, any>\n ? TTargetSchema extends ArraySchemaBuilder<any, any, any>\n ? CheckSchemaCompatible<\n ExtractArrayElementSchema<TSourceSchema>,\n ExtractArrayElementSchema<TTargetSchema>,\n TRegistered\n >\n : false\n : // Both objects → check registration\n TSourceSchema extends ObjectSchemaBuilder<any, any, any>\n ? TTargetSchema extends ObjectSchemaBuilder<any, any, any>\n ? [TSourceSchema, TTargetSchema] extends TRegistered\n ? true\n : false\n : false\n : false;\n\n/**\n * Checks whether the source property selected by `from()` is compatible\n * with the target property, evaluated *after* `TReturn` has been inferred.\n *\n * This decouples inference from validation: the `TReturn` constraint uses\n * `any` for setValue/getValue value types so TypeScript always infers\n * successfully, and this type performs the actual compatibility check\n * in the `_args` conditional spread.\n */\ntype IsFromCompatible<\n TReturn,\n TToSchema extends ObjectSchemaBuilder<any, any, any>,\n TKey extends string,\n TRegistered\n> = [ExtractPropertySchema<TReturn>] extends [never]\n ? false\n : CheckSchemaCompatible<\n ExtractPropertySchema<TReturn>,\n TargetPropertySchema<TToSchema, TKey>,\n TRegistered\n >;\n\n// ── Mapper Result Type ────────────────────────────────────────────────\n\nexport type SchemaToSchemaMapperResult<\n TFromSchema extends ObjectSchemaBuilder<any, any, any>,\n TToSchema extends ObjectSchemaBuilder<any, any, any>\n> = (from: InferType<TFromSchema>) => Promise<InferType<TToSchema>>;\n\n// ── Error Class ───────────────────────────────────────────────────────\n\nexport class MapperConfigurationError extends Error {\n constructor(messageOrUnmappedProperties: string | string[]) {\n super(\n typeof messageOrUnmappedProperties === 'string'\n ? messageOrUnmappedProperties\n : `Mapper configuration error: the following target properties are not mapped and not ignored: ${messageOrUnmappedProperties.join(', ')}`\n );\n this.name = 'MapperConfigurationError';\n }\n}\n\n// ── Internal Mapping Entry ────────────────────────────────────────────\n\ntype MappingEntry = {\n type: 'prop' | 'custom' | 'ignore' | 'auto' | 'autoArray';\n sourceDescriptorInner?: ReturnType<\n PropertyDescriptor<\n any,\n any,\n any\n >[typeof SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR]['getValue']\n > extends any\n ? any\n : never;\n fn?: (obj: any) => any;\n autoMapper?: SchemaToSchemaMapperResult<any, any>;\n elementMapper?: (element: any) => Promise<any>;\n};\n\n// ── Array Element Mapper Resolution ───────────────────────────────────\n\n/**\n * Resolves an element mapper for array properties.\n * Handles ObjectSchemaBuilder elements (via registry) and\n * primitive elements (direct copy when types match).\n * Returns null if no valid element mapper can be resolved.\n */\nfunction resolveElementMapper(\n sourceElementSchema: SchemaBuilder<any, any, any>,\n targetElementSchema: SchemaBuilder<any, any, any>,\n registry: MappingRegistry<any> | undefined\n): ((element: any) => Promise<any>) | null {\n // Both element schemas are ObjectSchemaBuilder: look up registered mapper\n if (\n sourceElementSchema instanceof ObjectSchemaBuilder &&\n targetElementSchema instanceof ObjectSchemaBuilder\n ) {\n if (registry) {\n const fromSchemaMappers =\n registry['_mappers'].get(sourceElementSchema);\n const autoMapper = fromSchemaMappers?.get(targetElementSchema);\n if (autoMapper) {\n return autoMapper as (element: any) => Promise<any>;\n }\n }\n // Same inferred structure: copy directly\n const fromKeys = Object.keys(\n sourceElementSchema.introspect().properties || {}\n ).sort();\n const toKeys = Object.keys(\n targetElementSchema.introspect().properties || {}\n ).sort();\n if (\n fromKeys.length === toKeys.length &&\n fromKeys.every((k, i) => k === toKeys[i])\n ) {\n return async (element: any) => element;\n }\n return null;\n }\n\n // Both element schemas are ArraySchemaBuilder: recursive element mapping\n if (\n sourceElementSchema instanceof ArraySchemaBuilder &&\n targetElementSchema instanceof ArraySchemaBuilder\n ) {\n const innerSourceElement =\n sourceElementSchema.introspect().elementSchema;\n const innerTargetElement =\n targetElementSchema.introspect().elementSchema;\n if (innerSourceElement && innerTargetElement) {\n const innerMapper = resolveElementMapper(\n innerSourceElement,\n innerTargetElement,\n registry\n );\n if (innerMapper) {\n return async (arr: any) => {\n if (arr == null) return undefined;\n if (!Array.isArray(arr)) return arr;\n return Promise.all(arr.map(innerMapper));\n };\n }\n }\n return null;\n }\n\n // Neither is ObjectSchemaBuilder nor ArraySchemaBuilder:\n // treat as primitive-compatible only when element schema types match.\n if (\n !(sourceElementSchema instanceof ObjectSchemaBuilder) &&\n !(targetElementSchema instanceof ObjectSchemaBuilder) &&\n !(sourceElementSchema instanceof ArraySchemaBuilder) &&\n !(targetElementSchema instanceof ArraySchemaBuilder)\n ) {\n const sourceIntrospection =\n typeof (sourceElementSchema as any).introspect === 'function'\n ? (sourceElementSchema as any).introspect()\n : undefined;\n const targetIntrospection =\n typeof (targetElementSchema as any).introspect === 'function'\n ? (targetElementSchema as any).introspect()\n : undefined;\n\n if (\n !sourceIntrospection ||\n !targetIntrospection ||\n sourceIntrospection.type !== targetIntrospection.type\n ) {\n return null;\n }\n return async (element: any) => element;\n }\n\n return null;\n}\n\n// ── PropertyMappingBuilder ────────────────────────────────────────────\n\n/**\n * Intermediate builder returned by `for()`. Provides three strategies\n * to configure how the selected target property is populated:\n * - `from()` — copy from a source property\n * - `compute()` — compute from the entire source object\n * - `ignore()` — explicitly skip the property\n */\nexport class PropertyMappingBuilder<\n TFromSchema extends ObjectSchemaBuilder<any, any, any>,\n TToSchema extends ObjectSchemaBuilder<any, any, any>,\n TKey extends string,\n TUnmapped extends string,\n TRegistered = never\n> {\n private readonly _mapper: Mapper<TFromSchema, TToSchema, any, TRegistered>;\n private readonly _targetKey: TKey;\n\n /** @internal */\n constructor(\n mapper: Mapper<TFromSchema, TToSchema, any, TRegistered>,\n targetKey: TKey\n ) {\n this._mapper = mapper;\n this._targetKey = targetKey;\n }\n\n /**\n * Maps the target property from a source property. The selector\n * receives the source schema's PropertyDescriptorTree and supports\n * nested paths (e.g. `(s) => s.address.city`).\n *\n * Type compatibility is enforced: only source properties whose\n * inferred type is assignable to the target property type will\n * appear in the selector callback.\n *\n * Under `strictFunctionTypes`, the `setValue` and `getValue` constraints\n * provide bidirectional type checking:\n * - `setValue` contravariance rejects source schemas with extra properties\n * - `getValue` covariance rejects source schemas with missing properties\n * - registered mappings widen the constraint via intersection\n */\n public from<\n TReturn extends {\n [SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR]: {\n getSchema(): SchemaBuilder<any, any, any>;\n setValue: (obj: any, value: any) => any;\n getValue: (obj: any) => {\n value?: any;\n success: boolean;\n };\n };\n }\n >(\n selector: (\n tree: PropertyDescriptorTree<\n TFromSchema,\n TFromSchema,\n SchemaPropertyInferredType<TToSchema, TKey>\n >\n ) => TReturn,\n ..._args: [TReturn] extends [never]\n ? [\n error: `Property '${TKey}': source property type is not assignable to the target property type. Use compute() instead.`\n ]\n : IsFromCompatible<\n TReturn,\n TToSchema,\n TKey,\n TRegistered\n > extends true\n ? []\n : [\n error: `Property '${TKey}': source property type is not assignable to the target property type. Use compute() instead.`\n ]\n ): Mapper<TFromSchema, TToSchema, Exclude<TUnmapped, TKey>, TRegistered> {\n const sourceTree = ObjectSchemaBuilder.getPropertiesFor(\n this._mapper['_fromSchema']\n );\n const sourceDescriptor = selector(sourceTree as any);\n const inner = sourceDescriptor[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR];\n\n // Check if both source and target property schemas are ObjectSchemaBuilder;\n // if so, look up the registered mapper and use it at runtime.\n const sourceSchema = inner.getSchema();\n const toProperties = (this._mapper['_toSchema'].introspect()\n .properties || {}) as Record<string, any>;\n const targetPropSchema = toProperties[this._targetKey];\n\n if (\n sourceSchema instanceof ObjectSchemaBuilder &&\n targetPropSchema instanceof ObjectSchemaBuilder &&\n this._mapper['_registry']\n ) {\n const fromSchemaMappers =\n this._mapper['_registry']['_mappers'].get(sourceSchema);\n const autoMapper = fromSchemaMappers?.get(targetPropSchema);\n if (autoMapper) {\n this._mapper['_mappings'].set(this._targetKey, {\n type: 'auto',\n sourceDescriptorInner: inner,\n autoMapper\n });\n return this._mapper as any;\n }\n }\n\n // Check if both source and target are ArraySchemaBuilder;\n // if so, resolve the element mapper and use element-wise mapping.\n if (\n sourceSchema instanceof ArraySchemaBuilder &&\n targetPropSchema instanceof ArraySchemaBuilder\n ) {\n const sourceElementSchema = (\n sourceSchema as ArraySchemaBuilder<any, any, any>\n ).introspect().elementSchema;\n const targetElementSchema = (\n targetPropSchema as ArraySchemaBuilder<any, any, any>\n ).introspect().elementSchema;\n\n if (sourceElementSchema && targetElementSchema) {\n const elementMapper = resolveElementMapper(\n sourceElementSchema,\n targetElementSchema,\n this._mapper['_registry']\n );\n if (elementMapper) {\n this._mapper['_mappings'].set(this._targetKey, {\n type: 'autoArray',\n sourceDescriptorInner: inner,\n elementMapper\n });\n return this._mapper as any;\n }\n }\n }\n\n this._mapper['_mappings'].set(this._targetKey, {\n type: 'prop',\n sourceDescriptorInner: inner\n });\n\n return this._mapper as any;\n }\n\n /**\n * Computes the target property value from the entire source object.\n * Supports both sync and async functions.\n */\n public compute(\n fn:\n | ((\n obj: InferType<TFromSchema>\n ) => SchemaPropertyInferredType<TToSchema, TKey>)\n | ((\n obj: InferType<TFromSchema>\n ) => Promise<SchemaPropertyInferredType<TToSchema, TKey>>)\n ): Mapper<TFromSchema, TToSchema, Exclude<TUnmapped, TKey>, TRegistered> {\n this._mapper['_mappings'].set(this._targetKey, {\n type: 'custom',\n fn: fn as any\n });\n\n return this._mapper as any;\n }\n\n /**\n * Explicitly excludes the target property from mapping.\n * The property will not appear in the output object.\n */\n public ignore(): Mapper<\n TFromSchema,\n TToSchema,\n Exclude<TUnmapped, TKey>,\n TRegistered\n > {\n this._mapper['_mappings'].set(this._targetKey, {\n type: 'ignore'\n });\n\n return this._mapper as any;\n }\n}\n\n// ── Mapper ────────────────────────────────────────────────────────────\n\n/**\n * A fluent builder for configuring how each target property is populated\n * from a source schema. Uses PropertyDescriptors as pointers to properties\n * (similar to expressions in C# .NET).\n *\n * The `TUnmapped` type parameter tracks which target properties have not\n * yet been mapped or ignored. `getMapper()` is only callable (without\n * arguments) when `TUnmapped` is `never` — i.e. all properties have been\n * accounted for. If any property is missing, TypeScript will produce a\n * compile-time type error (a type-assignability mismatch that includes\n * the unmapped property names in its type parameters).\n *\n * @typeParam TFromSchema - source ObjectSchemaBuilder\n * @typeParam TToSchema - target ObjectSchemaBuilder\n * @typeParam TUnmapped - union of target property key names not yet mapped\n */\nexport class Mapper<\n TFromSchema extends ObjectSchemaBuilder<any, any, any>,\n TToSchema extends ObjectSchemaBuilder<any, any, any>,\n TUnmapped extends string = SchemaKeys<TToSchema>,\n TRegistered = never\n> {\n /** Phantom property for structural type-checking of TUnmapped. */\n declare readonly [SYMBOL_UNMAPPED]: TUnmapped;\n\n private readonly _fromSchema: TFromSchema;\n private readonly _toSchema: TToSchema;\n private readonly _registry: MappingRegistry<any> | undefined;\n private readonly _mappings: Map<string, MappingEntry> = new Map();\n\n /**\n * Creates a new instance of the Mapper class.\n * @param fromSchema - `object` schema to map from\n * @param toSchema - `object` schema to map to\n * @param registry - optional MappingRegistry to auto-register the mapper\n */\n public constructor(\n fromSchema: TFromSchema,\n toSchema: TToSchema,\n registry?: MappingRegistry<any>\n ) {\n this._fromSchema = fromSchema;\n this._toSchema = toSchema;\n this._registry = registry;\n }\n\n /**\n * Selects a target property to configure. The selector callback\n * receives a tree of all target properties. Navigate by\n * property name: `(t) => t.cityName`.\n *\n * Auto-mappable properties (same name and compatible type, or\n * ObjectSchemaBuilder with a registered mapping) are also available\n * for explicit override.\n */\n public for<TKey extends SchemaKeys<TToSchema>>(\n selector: (\n tree: TargetPropertyTree<TToSchema, SchemaKeys<TToSchema>>\n ) => TargetPropertyKey<TKey>\n ): PropertyMappingBuilder<\n TFromSchema,\n TToSchema,\n TKey,\n TUnmapped,\n TRegistered\n > {\n // At runtime, use a Proxy to detect which property was accessed\n let capturedKey: string | undefined;\n const proxy = new Proxy({} as any, {\n get(_target, prop) {\n if (typeof prop === 'string') {\n capturedKey = prop;\n }\n return { [SYMBOL_TARGET_PROPERTY_KEY]: prop };\n }\n });\n\n selector(proxy);\n\n if (!capturedKey) {\n throw new Error(\n 'for selector must access a property on the target tree'\n );\n }\n\n // Validate that the captured key exists in the target schema\n const toIntrospection = this._toSchema.introspect();\n const targetProperties = toIntrospection.properties\n ? Object.keys(toIntrospection.properties)\n : [];\n if (!targetProperties.includes(capturedKey)) {\n throw new MapperConfigurationError(\n `Property \"${capturedKey}\" does not exist in the target schema`\n );\n }\n\n return new PropertyMappingBuilder(this, capturedKey as TKey);\n }\n\n /**\n * Returns the configured async mapping function.\n *\n * **Compile-time safety:** This method is only callable without\n * arguments when all target properties have been mapped or explicitly\n * ignored. If any property is unmapped, TypeScript will require\n * a string argument describing the unmapped properties, producing\n * a clear compile-time error.\n *\n * **Runtime safety:** Even if TypeScript checks are bypassed (e.g.\n * via `as any`), a `MapperConfigurationError` is thrown at runtime\n * listing the unmapped properties.\n */\n public getMapper(\n ..._args: [TUnmapped] extends [never]\n ? []\n : [error: `Unmapped properties: ${TUnmapped}`]\n ): SchemaToSchemaMapperResult<TFromSchema, TToSchema> {\n // Runtime validation: ensure all target properties are covered\n const toIntrospection = this._toSchema.introspect();\n const targetProperties = toIntrospection.properties\n ? Object.keys(toIntrospection.properties)\n : [];\n\n // Auto-mapping: fill in unmapped properties using registered nested mappers\n if (this._registry) {\n const fromIntrospection = this._fromSchema.introspect();\n const fromProperties = (fromIntrospection.properties ||\n {}) as Record<string, any>;\n const toProperties = (toIntrospection.properties || {}) as Record<\n string,\n any\n >;\n const fromTree = ObjectSchemaBuilder.getPropertiesFor(\n this._fromSchema\n );\n\n for (const key of targetProperties) {\n if (this._mappings.has(key)) continue;\n\n const fromPropSchema = fromProperties[key];\n const toPropSchema = toProperties[key];\n\n if (!fromPropSchema || !toPropSchema) continue;\n\n const sourceDescriptor = (fromTree as any)[key];\n if (!sourceDescriptor?.[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR])\n continue;\n\n // ObjectSchemaBuilder → ObjectSchemaBuilder: use registered mapper\n if (\n fromPropSchema instanceof ObjectSchemaBuilder &&\n toPropSchema instanceof ObjectSchemaBuilder\n ) {\n const fromSchemaMappers =\n this._registry['_mappers'].get(fromPropSchema);\n const autoMapper = fromSchemaMappers?.get(toPropSchema);\n if (autoMapper) {\n this._mappings.set(key, {\n type: 'auto',\n sourceDescriptorInner:\n sourceDescriptor[\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n ],\n autoMapper\n });\n continue;\n }\n\n // Same-name ObjectSchemaBuilder with identical property keys:\n // copy directly (full type safety ensured at compile time\n // via bidirectional InferType check)\n const fromKeys = Object.keys(\n fromPropSchema.introspect().properties || {}\n ).sort();\n const toKeys = Object.keys(\n toPropSchema.introspect().properties || {}\n ).sort();\n if (\n fromKeys.length === toKeys.length &&\n fromKeys.every((k, i) => k === toKeys[i])\n ) {\n this._mappings.set(key, {\n type: 'prop',\n sourceDescriptorInner:\n sourceDescriptor[\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n ]\n });\n }\n continue;\n }\n\n // Primitive → Primitive: auto-map same-name props\n // (type compatibility is ensured at the type level by\n // KeysNeedingMapping / NeedsMapping)\n if (\n !(fromPropSchema instanceof ObjectSchemaBuilder) &&\n !(toPropSchema instanceof ObjectSchemaBuilder) &&\n !(fromPropSchema instanceof ArraySchemaBuilder) &&\n !(toPropSchema instanceof ArraySchemaBuilder)\n ) {\n this._mappings.set(key, {\n type: 'prop',\n sourceDescriptorInner:\n sourceDescriptor[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR]\n });\n continue;\n }\n\n // ArraySchemaBuilder → ArraySchemaBuilder: use element mapper\n if (\n fromPropSchema instanceof ArraySchemaBuilder &&\n toPropSchema instanceof ArraySchemaBuilder\n ) {\n const sourceElementSchema =\n fromPropSchema.introspect().elementSchema;\n const targetElementSchema =\n toPropSchema.introspect().elementSchema;\n if (sourceElementSchema && targetElementSchema) {\n const elementMapper = resolveElementMapper(\n sourceElementSchema,\n targetElementSchema,\n this._registry\n );\n if (elementMapper) {\n this._mappings.set(key, {\n type: 'autoArray',\n sourceDescriptorInner:\n sourceDescriptor[\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n ],\n elementMapper\n });\n }\n }\n }\n }\n }\n\n const unmapped = targetProperties.filter(\n key => !this._mappings.has(key)\n );\n\n if (unmapped.length > 0) {\n throw new MapperConfigurationError(unmapped);\n }\n\n const targetTree = ObjectSchemaBuilder.getPropertiesFor(this._toSchema);\n\n const mappings = new Map(this._mappings);\n\n const mapperFn = async (\n source: InferType<TFromSchema>\n ): Promise<InferType<TToSchema>> => {\n const result = {} as any;\n\n for (const [key, entry] of mappings) {\n if (entry.type === 'ignore') {\n continue;\n }\n\n let value: any;\n\n if (entry.type === 'prop') {\n const getResult =\n entry.sourceDescriptorInner.getValue(source);\n if (getResult.success) {\n value = getResult.value;\n } else {\n continue;\n }\n } else if (entry.type === 'custom') {\n value = await entry.fn!(source);\n } else if (entry.type === 'auto') {\n const getResult =\n entry.sourceDescriptorInner.getValue(source);\n if (getResult.success && getResult.value !== undefined) {\n value = await entry.autoMapper!(getResult.value);\n } else {\n continue;\n }\n } else if (entry.type === 'autoArray') {\n const getResult =\n entry.sourceDescriptorInner.getValue(source);\n // null/undefined → skip (spec §6); non-array → error\n if (getResult.success && getResult.value != null) {\n if (!Array.isArray(getResult.value)) {\n throw new MapperConfigurationError(\n `Expected array for property \"${key}\" but got ${typeof getResult.value}`\n );\n }\n value = await Promise.all(\n getResult.value.map(entry.elementMapper!)\n );\n } else {\n continue;\n }\n }\n\n const targetDescriptor = (targetTree as any)[key];\n if (targetDescriptor?.[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR]) {\n targetDescriptor[\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n ].setValue(result, value, {\n createMissingStructure: true\n });\n } else {\n result[key] = value;\n }\n }\n\n return result;\n };\n\n // Auto-register in the registry if available\n if (this._registry) {\n this._registry['_mappers']\n .get(this._fromSchema)\n ?.set(this._toSchema, mapperFn);\n }\n\n return mapperFn;\n }\n}\n\n// ── MappingRegistry ───────────────────────────────────────────────────\n\nexport class MappingRegistry<TRegistered = never> {\n protected readonly _mappers: Map<\n ObjectSchemaBuilder<any, any, any>,\n Map<\n ObjectSchemaBuilder<any, any, any>,\n SchemaToSchemaMapperResult<any, any>\n >\n > = new Map();\n\n #ensureObjectSchemas(\n fromSchema: ObjectSchemaBuilder<any, any, any>,\n toSchema: ObjectSchemaBuilder<any, any, any>\n ): boolean {\n return !(\n !fromSchema ||\n !toSchema ||\n fromSchema instanceof ObjectSchemaBuilder === false ||\n toSchema instanceof ObjectSchemaBuilder === false\n );\n }\n\n /**\n * Defines a mapping between two schemas and returns a new immutable\n * registry containing the mapping. The callback `fn` receives a fresh\n * `Mapper` and must return it after configuring property mappings.\n * The mapper is automatically finalized and registered. Properties\n * not explicitly mapped or ignored may be auto-mapped if a matching\n * nested mapping is already registered in the registry.\n *\n * @param fromSchema - source ObjectSchemaBuilder\n * @param toSchema - target ObjectSchemaBuilder\n * @param fn - callback that configures property mappings on the mapper\n * @returns a new MappingRegistry containing all previous mappings plus\n * the newly configured one\n * @throws if schemas are invalid, mapping is duplicate, or unmapped\n * properties remain that cannot be auto-mapped\n */\n public configure<\n TFromSchema extends ObjectSchemaBuilder<any, any, any>,\n TToSchema extends ObjectSchemaBuilder<any, any, any>\n >(\n fromSchema: TFromSchema,\n toSchema: TToSchema,\n fn: (\n mapper: Mapper<\n TFromSchema,\n TToSchema,\n KeysNeedingMapping<TFromSchema, TToSchema, TRegistered>,\n TRegistered\n >\n ) => Mapper<TFromSchema, TToSchema, never, TRegistered>\n ): MappingRegistry<TRegistered | [TFromSchema, TToSchema]> {\n if (!this.#ensureObjectSchemas(fromSchema, toSchema)) {\n throw new Error(\n 'Both fromSchema and toSchema must be instances of ObjectSchemaBuilder'\n );\n }\n\n // Check for duplicate mappings\n const existingFromMappers = this._mappers.get(fromSchema);\n if (existingFromMappers?.has(toSchema)) {\n throw new Error(\n 'Duplicate mapping: a mapping for this schemas pair is already registered'\n );\n }\n\n // Create new immutable registry with cloned mappings\n const newRegistry = new MappingRegistry<\n TRegistered | [TFromSchema, TToSchema]\n >();\n for (const [from, toMap] of this._mappers) {\n newRegistry._mappers.set(from, new Map(toMap));\n }\n\n // Ensure the from→to map slot exists so getMapper can register\n if (!newRegistry._mappers.has(fromSchema)) {\n newRegistry._mappers.set(fromSchema, new Map());\n }\n\n // Create mapper with new registry reference\n const mapper = new Mapper<\n TFromSchema,\n TToSchema,\n KeysNeedingMapping<TFromSchema, TToSchema, TRegistered>,\n TRegistered\n >(fromSchema, toSchema, newRegistry);\n\n // Configure via user callback\n const configuredMapper = fn(mapper);\n\n // Implicit finalization: auto-map + validate + register\n (\n configuredMapper as Mapper<\n TFromSchema,\n TToSchema,\n never,\n TRegistered\n >\n ).getMapper();\n\n return newRegistry;\n }\n\n /**\n * Gets a mapper function that will map from the fromSchema to the toSchema.\n * Throws an error if no mapper is found for the given schemas pair.\n * @param fromSchema a schema to map from\n * @param toSchema a schema to map to\n * @returns a function that will take a value of the fromSchema type as an argument and\n * return a promise resolving to a value of the toSchema type\n */\n public getMapper<\n TFromSchema extends ObjectSchemaBuilder<any, any, any>,\n TToSchema extends ObjectSchemaBuilder<any, any, any>\n >(\n fromSchema: TFromSchema,\n toSchema: TToSchema\n ): SchemaToSchemaMapperResult<TFromSchema, TToSchema> {\n if (!this.#ensureObjectSchemas(fromSchema, toSchema)) {\n throw new Error(\n 'Both fromSchema and toSchema must be instances of ObjectSchemaBuilder'\n );\n }\n\n const mappers = this._mappers.get(fromSchema);\n if (!mappers) {\n throw new Error('No mapper found for the given schemas pair');\n }\n const mapper = mappers.get(toSchema);\n if (!mapper) {\n throw new Error('No mapper found for the given schemas pair');\n }\n\n return mapper;\n }\n}\n\n/**\n * Creates a new empty {@link MappingRegistry}.\n *\n * This is a convenience factory function — an alternative to\n * `new MappingRegistry()` that reads better in a fluent chain:\n *\n * ```ts\n * const registry = mapper()\n * .configure(A, B, m => ...)\n * .configure(C, D, m => ...);\n * ```\n */\nexport function mapper(): MappingRegistry {\n return new MappingRegistry();\n}\n"],"mappings":"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","names":["ArraySchemaBuilder","ObjectSchemaBuilder","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","SYMBOL_TARGET_PROPERTY_KEY","MapperConfigurationError","messageOrUnmappedProperties","resolveElementMapper","sourceElementSchema","targetElementSchema","registry","ObjectSchemaBuilder","autoMapper","fromKeys","toKeys","k","i","element","ArraySchemaBuilder","innerSourceElement","innerTargetElement","innerMapper","arr","sourceIntrospection","targetIntrospection","PropertyMappingBuilder","mapper","targetKey","selector","_args","sourceTree","inner","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","sourceSchema","targetPropSchema","elementMapper","fn","Mapper","fromSchema","toSchema","capturedKey","proxy","_target","prop","SYMBOL_TARGET_PROPERTY_KEY","toIntrospection","targetProperties","fromProperties","toProperties","fromTree","key","fromPropSchema","toPropSchema","sourceDescriptor","unmapped","targetTree","mappings","mapperFn","source","result","entry","value","getResult","targetDescriptor","MappingRegistry","_MappingRegistry","#ensureObjectSchemas","newRegistry","from","toMap","mappers"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"author": "Andrew Zolotukhin <andrew_zol@cleverbrush.com>",
|
|
3
|
+
"bugs": {
|
|
4
|
+
"url": "https://github.com/cleverbrush/framework/issues",
|
|
5
|
+
"email": "andrew_zol@cleverbrush.com"
|
|
6
|
+
},
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@cleverbrush/schema": "0.0.0-beta-20260410073748"
|
|
9
|
+
},
|
|
10
|
+
"description": "Type-safe, schema-driven object mapper for @cleverbrush/schema — compile-time completeness, selector-based field mapping, auto-mapping",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://docs.cleverbrush.com/modules/_cleverbrush_mapper.html",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"object mapper",
|
|
17
|
+
"schema mapping",
|
|
18
|
+
"typescript",
|
|
19
|
+
"type-safe",
|
|
20
|
+
"data transformation",
|
|
21
|
+
"cleverbrush"
|
|
22
|
+
],
|
|
23
|
+
"license": "BSD 3-Clause",
|
|
24
|
+
"main": "./dist/index.js",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"name": "@cleverbrush/mapper",
|
|
33
|
+
"readme": "https://github.com/cleverbrush/framework/tree/master/libs/mapper#readme",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "github:cleverbrush/framework"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"watch": "tsc --build tsconfig.build.json --watch",
|
|
40
|
+
"build": "tsup && tsc --project tsconfig.build.json --emitDeclarationOnly",
|
|
41
|
+
"clean": "rm -rf dist tsconfig.build.tsbuildinfo"
|
|
42
|
+
},
|
|
43
|
+
"type": "module",
|
|
44
|
+
"types": "./dist/index.d.ts",
|
|
45
|
+
"version": "0.0.0-beta-20260410073748"
|
|
46
|
+
}
|