Skip to content

Commit a4a85c8

Browse files
os-zhuangclaude
andauthored
refactor(spec)!: FieldMapping no longer names three declarations (#4703, C12) (#4710)
`FieldMapping` / `FieldMappingSchema` were published by THREE entry points for three different declarations — the #4411 trap, one entry worse than the usual pair: ./shared the base, plain z.object, 4 keys ./integration Base.extend({ dataType, required, syncMode }), 7 keys ./data an independent strictObject, 4 keys — and a different CONCEPT: the column mapping of a CSV/table import, not a connector's remote-field mapping Per ADR-0112 D9(a) the two domain-specific sides take a domain prefix and the base keeps the bare name: integration/FieldMapping -> integration/ConnectorFieldMapping data/FieldMapping -> data/ImportFieldMapping `shared/FieldMapping` is untouched — two other defs extend it, including `data/ExternalFieldMapping`, which has never been in the baseline precisely because it already carries a domain prefix. dual-source-exports baseline: 16 -> 14. Zero tombstones and zero ADR-0087 conversions: all eleven authorable keys (7 + 4) carry over unchanged, so no authored metadata migrates. The rename is carried through the two def-keyed ratchets by `RENAMED_DEFS` (#4684), which gets its first entries beyond the original one — and with them the first two rules that only bind when the table holds more than one entry: two sources onto one target is rejected as a merge (it would collapse the carried key sets and their retired states, blinding the "live -> retired needs a conversion" check), and a chained rename is rejected by name rather than misdiagnosed as a typo. Regression pins live in `src/integration/connector.test.ts` next to the #4684 block: a TypeScript compiler-API symbol-identity resolution over all three pairs of entries (types are erased at runtime, and #4642 proved a compile-time pin in this package is dead text), plus three runtime pins on the concept differences that justify the rename — `transform`'s union-vs-enum split, `./data`'s array cardinality, and its strictObject throwing where the other two silently strip. The C9 `KNOWN_STILL_DUAL_SOURCE` handshake list is now empty. `gen:docs` moves the connector field mapping from a phantom `references/integration/mapping` page into `references/integration/connector`, where the schema actually lives — #4696's bare-name global index resolving now that the names are distinct. Claude-Session: https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 16fc124 commit a4a85c8

16 files changed

Lines changed: 635 additions & 100 deletions
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
---
2+
"@objectstack/spec": major
3+
---
4+
5+
BREAKING(spec): `FieldMapping` named three declarations — the two domain-specific
6+
sides are renamed to `ConnectorFieldMapping` and `ImportFieldMapping` (#4703, #4535 C12)
7+
8+
`FieldMapping` / `FieldMappingSchema` were exported by **three** entry points for
9+
**three different declarations**, so which type you got depended only on the import
10+
path — the #4411 trap, one entry worse than the usual pair:
11+
12+
| entry | declaration | keys | shape |
13+
|:--|:--|:--|:--|
14+
| `@objectstack/spec/shared` (**unchanged**) | `shared/mapping.zod.ts` | 4 | the base — plain `z.object` |
15+
| `@objectstack/spec/integration` (**renamed**) | `integration/connector.zod.ts` | 7 | `Base.extend({ dataType, required, syncMode })` |
16+
| `@objectstack/spec/data` (**renamed**) | `data/mapping.zod.ts` | 4 | an independent `strictObject` |
17+
18+
The first two are base-and-superset. The third is **not the same concept at all**: it
19+
is the column mapping of a CSV/table import (`mapping.fieldMapping[]`), not a
20+
connector's remote-field mapping. Three ways the two are mutually unparseable:
21+
22+
1. **`transform` is the same key name with incompatible value types.** `shared` /
23+
`integration` take the discriminated union `FieldMappingTransformSchema`
24+
(`{ type: 'cast', targetType: 'string' }`); `data` takes a flat `TransformType`
25+
enum defaulting to `'none'`, steering a separate `params` bag.
26+
2. **Different cardinality.** `data` accepts `string | string[]` for `source` and
27+
`target` — one target field may be composed from several columns (`split` /
28+
`join`). The other two accept a single `string`.
29+
3. **Opposite failure modes for an unknown key.** `data` is a `strictObject`
30+
(#4001): it **throws**, naming the canonical spelling. The other two are plain
31+
`z.object`: they **strip silently**. Under one shared name, the same typo is a
32+
hard error in one domain and a no-op in the other.
33+
34+
Per **ADR-0112 D9(a)** the domain-specific sides take a domain prefix and the base
35+
keeps the bare name — the same ruling that produced `ConnectorRateLimitConfig`
36+
(#4684), `ConnectorErrorCategory` and `ConnectorRetryStrategy`. This is not a new
37+
convention: `data/ExternalFieldMappingSchema` already extends the same base and,
38+
purely because it carries a prefix, never entered the dual-source baseline at all.
39+
40+
The dual-source baseline shrinks **16 → 14**.
41+
42+
## FROM → TO
43+
44+
```ts
45+
// before — @objectstack/spec/integration
46+
import { FieldMappingSchema, type FieldMapping } from '@objectstack/spec/integration';
47+
// after
48+
import {
49+
ConnectorFieldMappingSchema,
50+
type ConnectorFieldMapping,
51+
} from '@objectstack/spec/integration';
52+
53+
// before — @objectstack/spec/data
54+
import { FieldMappingSchema, type FieldMapping } from '@objectstack/spec/data';
55+
// after
56+
import {
57+
ImportFieldMappingSchema,
58+
type ImportFieldMapping,
59+
} from '@objectstack/spec/data';
60+
```
61+
62+
**Importing from `@objectstack/spec/shared`? Nothing changes** — that `FieldMapping`
63+
is the base, keeps its name, its four keys and its plain-`z.object` behaviour.
64+
65+
No deprecated aliases are kept on either renamed entry: re-exporting the old name
66+
would be a third declaration of it and would re-open the trap this change closes.
67+
68+
⚠️ **Do not "fix" the compile error by re-pointing the import at
69+
`@objectstack/spec/shared`.** That name resolves, and it is the wrong schema. On the
70+
connector side it silently costs you `dataType` / `required` / `syncMode` — the base
71+
is not `.strict()`, so those keys are **stripped at parse time** and the mapping runs
72+
without them. On the import side the base rejects arrays and the enum form of
73+
`transform` outright. Take the prefixed name for the domain you are in.
74+
75+
## Authored metadata needs no migration
76+
77+
This renames TypeScript exports and two internal JSON Schema `$def`s — **not a single
78+
authorable key**. All eleven keys carry over unchanged, verified by the
79+
`authorable-surface.json` ratchet rather than by inspection:
80+
81+
- `connectors[].fieldMappings[]``source`, `target`, `transform`, `defaultValue`,
82+
`dataType`, `required`, `syncMode` (7)
83+
- `mapping.fieldMapping[]``source`, `target`, `transform`, `params` (4)
84+
85+
Same names, same types, same defaults, same strictness. Existing stack metadata,
86+
stored `sys_metadata` rows and published apps are byte-for-byte unaffected, which is
87+
why this ships with **no ADR-0087 conversion and no tombstone**: nothing was retired.
88+
The `major` is for the two renamed TypeScript exports alone — the only edit an upgrade
89+
needs is the import above.
90+
91+
The published JSON Schema `$id`s move with the defs:
92+
`…/integration/FieldMapping.json``…/integration/ConnectorFieldMapping.json`, and
93+
`…/data/FieldMapping.json``…/data/ImportFieldMapping.json`.
94+
95+
## Gate change riding along
96+
97+
`scripts/lib/renamed-defs.ts` (the #4684 carry-over table) gets its first entries
98+
beyond the original one, and with them the first rules that only bind when the table
99+
holds **more than one**:
100+
101+
- **two sources onto one target is rejected.** That is a merge, not two renames, and
102+
it defeats the table's purpose: `build-schemas.ts` carries the snapshot into a map
103+
keyed by the *new* key, so two defs' entries for one property name collapse — and
104+
the surviving `[RETIRED]` state is whichever was carried last. A key live under one
105+
def and tombstoned under the other would then read as already-retired, and the
106+
"every live → retired transition needs a registered conversion" check would never
107+
fire for it.
108+
- **a chained rename (A → B → C) is rejected by name.** It was already red as
109+
"B is not emitted", which is true but misdiagnoses it as a typo; the carry is a
110+
single pass, so chains are unsupported outright.

content/docs/getting-started/quick-reference.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Core business logic and data modeling schemas.
2525
| **[Validation](/docs/references/data/validation)** | `validation.zod.ts` | ValidationRule | Business validation rules |
2626
| **[Datasource](/docs/references/data/datasource)** | `datasource.zod.ts` | Datasource, DriverDefinition | Database connection configs |
2727
| **[Analytics](/docs/references/data/analytics)** | `analytics.zod.ts` | Analytics | Data analytics and aggregation |
28-
| **[Mapping](/docs/references/data/mapping)** | `mapping.zod.ts` | FieldMapping | Field transformation mappings |
28+
| **[Mapping](/docs/references/data/mapping)** | `mapping.zod.ts` | ImportFieldMapping | Field transformation mappings |
2929
| **[Hook](/docs/references/data/hook)** | `hook.zod.ts` | Hook, HookEvent | Lifecycle event hooks |
3030
| **[Data Engine](/docs/references/data/data-engine)** | `data-engine.zod.ts` | DataEngine | Data engine configuration |
3131
| **[Driver](/docs/references/data/driver)** | `driver.zod.ts` | Driver, DriverCapabilities | Database driver interface |

content/docs/references/data/mapping.mdx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,16 @@ green run.
2424
## TypeScript Usage
2525

2626
```typescript
27-
import { FieldMappingSchema, MappingSchema, TransformType } from '@objectstack/spec/data';
28-
import type { FieldMapping, Mapping } from '@objectstack/spec/data';
27+
import { ImportFieldMappingSchema, MappingSchema, TransformType } from '@objectstack/spec/data';
28+
import type { ImportFieldMapping, Mapping } from '@objectstack/spec/data';
2929

3030
// Validate data
31-
const result = FieldMappingSchema.parse(data);
31+
const result = ImportFieldMappingSchema.parse(data);
3232
```
3333

3434
---
3535

36-
## FieldMapping
36+
## ImportFieldMapping
3737

3838
### Properties
3939

content/docs/references/integration/connector.mdx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ with simple `auth` — or by `[automation/sync.zod.ts](/docs/references/automati
132132
## TypeScript Usage
133133

134134
```typescript
135-
import { CircuitBreakerConfigSchema, ConnectorSchema, ConnectorActionSchema, ConnectorErrorCategorySchema, ConnectorHealthSchema, ConnectorRateLimitConfigSchema, ConnectorRetryStrategySchema, ConnectorStatusSchema, ConnectorTriggerSchema, ConnectorTypeSchema, DataSyncConfigSchema, DeclarativeConnectorEntrySchema, ErrorMappingConfigSchema, ErrorMappingRuleSchema, HealthCheckConfigSchema, RateLimitStrategySchema, RetryConfigSchema, SyncStrategySchema, WebhookConfigSchema, WebhookEventSchema, WebhookSignatureAlgorithmSchema } from '@objectstack/spec/integration';
136-
import type { CircuitBreakerConfig, Connector, ConnectorErrorCategory, ConnectorHealth, ConnectorRateLimitConfig, ConnectorRetryStrategy, ConnectorStatus, ConnectorType, DataSyncConfig, DeclarativeConnectorEntry, ErrorMappingConfig, ErrorMappingRule, HealthCheckConfig, RateLimitStrategy, RetryConfig, SyncStrategy, WebhookConfig, WebhookEvent, WebhookSignatureAlgorithm } from '@objectstack/spec/integration';
135+
import { CircuitBreakerConfigSchema, ConnectorSchema, ConnectorActionSchema, ConnectorErrorCategorySchema, ConnectorFieldMappingSchema, ConnectorHealthSchema, ConnectorRateLimitConfigSchema, ConnectorRetryStrategySchema, ConnectorStatusSchema, ConnectorTriggerSchema, ConnectorTypeSchema, DataSyncConfigSchema, DeclarativeConnectorEntrySchema, ErrorMappingConfigSchema, ErrorMappingRuleSchema, HealthCheckConfigSchema, RateLimitStrategySchema, RetryConfigSchema, SyncStrategySchema, WebhookConfigSchema, WebhookEventSchema, WebhookSignatureAlgorithmSchema } from '@objectstack/spec/integration';
136+
import type { CircuitBreakerConfig, Connector, ConnectorErrorCategory, ConnectorFieldMapping, ConnectorHealth, ConnectorRateLimitConfig, ConnectorRetryStrategy, ConnectorStatus, ConnectorType, DataSyncConfig, DeclarativeConnectorEntry, ErrorMappingConfig, ErrorMappingRule, HealthCheckConfig, RateLimitStrategy, RetryConfig, SyncStrategy, WebhookConfig, WebhookEvent, WebhookSignatureAlgorithm } from '@objectstack/spec/integration';
137137

138138
// Validate data
139139
const result = CircuitBreakerConfigSchema.parse(data);
@@ -223,6 +223,23 @@ Standard error category
223223
* `integration_error`
224224

225225

226+
---
227+
228+
## ConnectorFieldMapping
229+
230+
### Properties
231+
232+
| Property | Type | Required | Description |
233+
| :--- | :--- | :--- | :--- |
234+
| **source** | `string` || Source field name |
235+
| **target** | `string` || Target field name |
236+
| **transform** | `{ type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record<string, any> }` | optional | Transformation to apply |
237+
| **defaultValue** | `any` | optional | Default if source is null/undefined |
238+
| **dataType** | `Enum<'string' \| 'number' \| 'boolean' \| 'date' \| 'datetime' \| 'json' \| 'array'>` | optional | Target data type |
239+
| **required** | `boolean` | optional | Field is required |
240+
| **syncMode** | `Enum<'read_only' \| 'write_only' \| 'bidirectional'>` | optional | Sync mode |
241+
242+
226243
---
227244

228245
## ConnectorHealth

content/docs/references/integration/mapping.mdx

Lines changed: 0 additions & 36 deletions
This file was deleted.

content/docs/references/integration/meta.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
"---Connectors---",
55
"connector",
66
"connector-auth",
7-
"mapping",
87
"---Transport & Storage---",
98
"offline"
109
]

packages/spec/api-surface.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -370,8 +370,6 @@
370370
"FieldGroupCollapse (type)",
371371
"FieldGroupSection (interface)",
372372
"FieldInput (type)",
373-
"FieldMapping (type)",
374-
"FieldMappingSchema (const)",
375373
"FieldNode (type)",
376374
"FieldNodeSchema (const)",
377375
"FieldOperators (type)",
@@ -411,6 +409,8 @@
411409
"IMPORT_BOOLEAN_TRUE_TOKENS (const)",
412410
"IMPORT_REFERENCE_TYPES (const)",
413411
"INSTANT_TYPES (const)",
412+
"ImportFieldMapping (type)",
413+
"ImportFieldMappingSchema (const)",
414414
"IndexSchema (const)",
415415
"InstantValueSchema (const)",
416416
"JSONValidation (type)",
@@ -3924,6 +3924,8 @@
39243924
"ConnectorDescriptor (interface)",
39253925
"ConnectorErrorCategory (type)",
39263926
"ConnectorErrorCategorySchema (const)",
3927+
"ConnectorFieldMapping (type)",
3928+
"ConnectorFieldMappingSchema (const)",
39273929
"ConnectorHealth (type)",
39283930
"ConnectorHealthSchema (const)",
39293931
"ConnectorInput (type)",
@@ -3958,8 +3960,6 @@
39583960
"ErrorMappingConfigSchema (const)",
39593961
"ErrorMappingRule (type)",
39603962
"ErrorMappingRuleSchema (const)",
3961-
"FieldMapping (type)",
3962-
"FieldMappingSchema (const)",
39633963
"HealthCheckConfig (type)",
39643964
"HealthCheckConfigSchema (const)",
39653965
"RateLimitStrategy (type)",

packages/spec/authorable-surface.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3483,10 +3483,6 @@
34833483
"data/Field:unique",
34843484
"data/Field:visibleWhen",
34853485
"data/Field:widget",
3486-
"data/FieldMapping:params",
3487-
"data/FieldMapping:source",
3488-
"data/FieldMapping:target",
3489-
"data/FieldMapping:transform",
34903486
"data/FieldReference:$field",
34913487
"data/FilePersistenceConfig:autoSaveInterval",
34923488
"data/FilePersistenceConfig:path",
@@ -3537,6 +3533,10 @@
35373533
"data/HookContext:session",
35383534
"data/HookContext:transaction",
35393535
"data/HookContext:user",
3536+
"data/ImportFieldMapping:params",
3537+
"data/ImportFieldMapping:source",
3538+
"data/ImportFieldMapping:target",
3539+
"data/ImportFieldMapping:transform",
35403540
"data/Index:fields",
35413541
"data/Index:name",
35423542
"data/Index:partial",
@@ -4141,6 +4141,13 @@
41414141
"integration/ConnectorAction:key",
41424142
"integration/ConnectorAction:label",
41434143
"integration/ConnectorAction:outputSchema",
4144+
"integration/ConnectorFieldMapping:dataType",
4145+
"integration/ConnectorFieldMapping:defaultValue",
4146+
"integration/ConnectorFieldMapping:required",
4147+
"integration/ConnectorFieldMapping:source",
4148+
"integration/ConnectorFieldMapping:syncMode",
4149+
"integration/ConnectorFieldMapping:target",
4150+
"integration/ConnectorFieldMapping:transform",
41444151
"integration/ConnectorHealth:circuitBreaker",
41454152
"integration/ConnectorHealth:healthCheck",
41464153
"integration/ConnectorInstanceAPIKeyAuth:credentialRef",
@@ -4207,13 +4214,6 @@
42074214
"integration/ErrorMappingRule:targetCategory",
42084215
"integration/ErrorMappingRule:targetCode",
42094216
"integration/ErrorMappingRule:userMessage",
4210-
"integration/FieldMapping:dataType",
4211-
"integration/FieldMapping:defaultValue",
4212-
"integration/FieldMapping:required",
4213-
"integration/FieldMapping:source",
4214-
"integration/FieldMapping:syncMode",
4215-
"integration/FieldMapping:target",
4216-
"integration/FieldMapping:transform",
42174217
"integration/HealthCheckConfig:enabled",
42184218
"integration/HealthCheckConfig:endpoint",
42194219
"integration/HealthCheckConfig:expectedStatus",

packages/spec/dual-source-exports.baseline.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
"EnvironmentArtifactInput — [./cloud (type)] ≠ [./system (type)]",
1111
"EnvironmentArtifactSchema — [./cloud (const)] ≠ [./system (const)]",
1212
"EventSchema — [./automation (const)] ≠ [./kernel (const)]",
13-
"FieldMapping — [./data (type)] ≠ [./integration (type)] ≠ [./shared (type)]",
14-
"FieldMappingSchema — [./data (const)] ≠ [./integration (const)] ≠ [./shared (const)]",
1513
"HttpMethod — [./api, ./shared (type)] ≠ [./ui (type)]",
1614
"PackageDependency — [./cloud (type)] ≠ [./kernel (type)]",
1715
"PackageDependencySchema — [./cloud (const)] ≠ [./kernel (const)]",

packages/spec/json-schema.manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,6 @@
753753
"data/FeedFilterMode",
754754
"data/FeedItemType",
755755
"data/Field",
756-
"data/FieldMapping",
757756
"data/FieldNode",
758757
"data/FieldReference",
759758
"data/FieldType",
@@ -769,6 +768,7 @@
769768
"data/HookBodyCapability",
770769
"data/HookContext",
771770
"data/HookEvent",
771+
"data/ImportFieldMapping",
772772
"data/Index",
773773
"data/InstantValue",
774774
"data/JSONValidation",
@@ -872,6 +872,7 @@
872872
"integration/Connector",
873873
"integration/ConnectorAction",
874874
"integration/ConnectorErrorCategory",
875+
"integration/ConnectorFieldMapping",
875876
"integration/ConnectorHealth",
876877
"integration/ConnectorInstanceAPIKeyAuth",
877878
"integration/ConnectorInstanceAuth",
@@ -887,7 +888,6 @@
887888
"integration/DeclarativeConnectorEntry",
888889
"integration/ErrorMappingConfig",
889890
"integration/ErrorMappingRule",
890-
"integration/FieldMapping",
891891
"integration/HealthCheckConfig",
892892
"integration/RateLimitStrategy",
893893
"integration/RetryConfig",

0 commit comments

Comments
 (0)