Skip to content

Commit 4b6cac7

Browse files
os-zhuangclaude
andauthored
feat(spec)!: resolve the three cross-form dual-source names (#4539) (#4571)
The three baseline rows where the two declarations sharing a name did not even share a FORM (type vs const, or two unrelated functions), so a wrong import-path pick had no shape overlap to hide behind. Each judged against a three-repo import-level scan (framework, cloud, objectui — the latter two contain zero references to all three names). - ShareRecipientType: the security zod enum (authorable sharing-RULE recipients) keeps the name; the contracts type is a DIFFERENT concept — the recipient_type a sys_record_share ROW may carry — renamed RecordShareRecipientType, member set aligned to the storage select it actually mirrors ('role', never persistable, -> 'position', completing the #2724 D3 rename its changelog already claimed). plugin-sharing re-export renamed in lockstep; only 'user' is enforced today, unchanged. - TransformType: the data import-mapping enum keeps the name; shared's differently-shaped config-union pair (zero importers for either name in all three repos) renamed FieldMappingTransformSchema / FieldMappingTransform. Renamed rather than alias-deleted because build-docs derives `import type` examples by stripping 'Schema' (filed unassigned as #4570). json-schema.manifest key moved with it. - suggestFieldType: diffed the two implementations FIRST — they were never forks of one function: shared/suggestions.zod is the typo-suggester (string -> string[] candidates, feeds the zod error map, keeps the name on . and ./shared); data/type-compat is the deterministic SQL-column -> FieldType mapper (ADR-0015), renamed suggestFieldTypeForSqlType. Divergence pinned in type-compat.test.ts: ('varchar(255)') -> [] vs 'text'; ('text_area') -> ['textarea'] vs undefined; ('int') -> ['number'] vs 'number'. Not a live bug: each caller imports the one it means; the hazard was authoring-time wrong-pick, where `[]` is truthy. All 3 dual-source-exports.baseline.json rows deleted; api-surface and reference docs regenerated via check:generated --fix (8/8 green); check:dual-source-exports green at 35 baseline rows. Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL Co-authored-by: Claude <noreply@anthropic.com>
1 parent c2c5e90 commit 4b6cac7

16 files changed

Lines changed: 224 additions & 58 deletions

File tree

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
"@objectstack/spec": major
3+
"@objectstack/plugin-sharing": major
4+
---
5+
6+
feat(spec)!: resolve the three cross-form dual-source names — ShareRecipientType, TransformType, suggestFieldType (#4539)
7+
8+
Three `dual-source-exports.baseline.json` rows where the two declarations
9+
sharing a name did not even share a FORM (type vs const, or two unrelated
10+
functions), so a wrong import-path pick had no shape overlap to hide behind
11+
and failed far from the cause. Each judged against a three-repo import-level
12+
scan (framework, cloud, objectui — the latter two contained zero references
13+
to all three names). All three rows are deleted from the baseline.
14+
15+
**Renamed — `./contracts` `ShareRecipientType``RecordShareRecipientType`:**
16+
17+
Two live concepts shared the name. The security zod enum
18+
(`user | team | position | unit_and_subordinates | business_unit`) is the
19+
authorable sharing-RULE recipient vocabulary and keeps the name. The contracts
20+
type describes a different thing — the `recipient_type` a `sys_record_share`
21+
ROW may carry — and its claim to "mirror spec/security" had been false since
22+
`group``team`/`guest` were retired there. Its member set is now aligned to
23+
the storage-side gate it actually mirrors, the `SysRecordShare`
24+
`recipient_type` select: `role` (never persistable, zero producers) is
25+
replaced by `position`. Only `user` is enforced (and written) today;
26+
`ISharingService.grant` keeps refusing every other value (ADR-0078).
27+
Fix: `import type { ShareRecipientType } from '@objectstack/spec/contracts'`
28+
(or from `@objectstack/plugin-sharing`, whose re-export is renamed in
29+
lockstep) → `RecordShareRecipientType`; code that named the `'role'` member
30+
was describing a value no row could ever hold — use the rule vocabulary
31+
(`SharingRuleRecipientType`) if a role recipient was meant.
32+
33+
**Renamed — `./shared` `TransformTypeSchema` / `TransformType`
34+
`FieldMappingTransformSchema` / `FieldMappingTransform`:**
35+
36+
`./data`'s `TransformType` (the authorable import-mapping enum
37+
`none | constant | lookup | split | join | javascript | map`) is the live
38+
declaration and keeps the name. `./shared` exported `TransformType` as the
39+
inferred type of `TransformTypeSchema` — a differently-shaped discriminated
40+
union of transform CONFIG objects — with zero importers for either name in
41+
all three repos. The shared pair is renamed (not just the alias deleted):
42+
the docs generator derives `import type { X }` examples by stripping
43+
`Schema` from each schema const, so an alias-less `TransformTypeSchema`
44+
would have kept generating a reference to an export that no longer exists.
45+
Fix: `TransformTypeSchema``FieldMappingTransformSchema`,
46+
`import type { TransformType } from '@objectstack/spec/shared'`
47+
`FieldMappingTransform` (same shape); importers who meant the import-mapping
48+
enum import `TransformType` from `@objectstack/spec/data`.
49+
50+
**Renamed — `./data` `suggestFieldType``suggestFieldTypeForSqlType`:**
51+
52+
The only function-kind dual-source. The two implementations were never forks
53+
of one function — different signatures, semantics and return types:
54+
`shared/suggestions.zod.ts` (kept on `.` / `./shared` under the original
55+
name) is the typo-suggester for an invalid authored FieldType
56+
(`(input: string) => string[]`, alias table + Levenshtein, feeds the zod
57+
error map), while `data/type-compat.ts` is the deterministic SQL-column →
58+
FieldType mapper for external-datasource drafts
59+
(`(rawType, dialect?) => FieldType | undefined`, ADR-0015 §4.6). Same input,
60+
divergent outputs — `('varchar(255)')``[]` vs `'text'`; `('text_area')`
61+
`['textarea']` vs `undefined`; `('int')``['number']` vs `'number'` — and
62+
the wrong pick compiled wherever the result was only truthiness-checked
63+
(`[]` is truthy). Behavioral divergence is now pinned in
64+
`data/type-compat.test.ts`.
65+
Fix: `import { suggestFieldType } from '@objectstack/spec/data'`
66+
`suggestFieldTypeForSqlType` (same signature); imports from the root entry
67+
or `./shared` are unaffected.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ Common utilities used across all protocols.
214214
| **[Expression](/docs/references/shared/expression)** | `expression.zod.ts` | Expression, ExpressionInput | CEL expression values and inputs |
215215
| **[HTTP](/docs/references/shared/http)** | `http.zod.ts` | HttpRequest, HttpMethod, CorsConfig | HTTP utilities |
216216
| **[Identifiers](/docs/references/shared/identifiers)** | `identifiers.zod.ts` | SystemIdentifier, SnakeCaseIdentifier | Standard ID patterns |
217-
| **[Mapping](/docs/references/shared/mapping)** | `mapping.zod.ts` | FieldMapping, TransformType | Field mapping utilities |
217+
| **[Mapping](/docs/references/shared/mapping)** | `mapping.zod.ts` | FieldMapping, FieldMappingTransform | Field mapping utilities |
218218
| **[Connector Auth](/docs/references/shared/connector-auth)** | `connector-auth.zod.ts` | ConnectorAuthConfig | Connector auth patterns |
219219

220220
## QA Protocol (1 schema)

content/docs/references/shared/mapping.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ defaultValue: 'Unknown'
6262
## TypeScript Usage
6363

6464
```typescript
65-
import { FieldMapping, TransformType } from '@objectstack/spec/shared';
66-
import type { FieldMapping, TransformType } from '@objectstack/spec/shared';
65+
import { FieldMapping, FieldMappingTransform } from '@objectstack/spec/shared';
66+
import type { FieldMapping, FieldMappingTransform } from '@objectstack/spec/shared';
6767

6868
// Validate data
6969
const result = FieldMapping.parse(data);
@@ -85,7 +85,7 @@ const result = FieldMapping.parse(data);
8585

8686
---
8787

88-
## TransformType
88+
## FieldMappingTransform
8989

9090
### Union Options
9191

packages/plugins/plugin-sharing/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export type {
7171
GrantShareInput,
7272
SharingExecutionContext,
7373
ShareAccessLevel,
74-
ShareRecipientType,
74+
RecordShareRecipientType,
7575
ShareSource,
7676
SharingRuleRow,
7777
DefineSharingRuleInput,

packages/plugins/plugin-sharing/src/objects/sys-record-share.object.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';
2222
*
2323
* Conventions:
2424
* - `object_name` is the short object name (e.g. `account`, `lead`).
25-
* - `recipient_type` mirrors `ShareRecipientType` from the spec
26-
* (`user` is enforced today; `group`/`position` are persisted for
27-
* forward-compatibility).
25+
* - `recipient_type` mirrors `RecordShareRecipientType` from
26+
* `@objectstack/spec/contracts` (`user` is enforced today;
27+
* `group`/`position` are persisted for forward-compatibility).
2828
* - `source = 'manual'` rows are created by a user via the REST
2929
* `POST /data/:object/:id/shares` endpoint. `source = 'rule'` rows
3030
* are materialised by the sharing-rule evaluator (future); the

packages/plugins/plugin-sharing/src/sharing-service.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -939,10 +939,12 @@ describe('[ADR-0111 D7] no inert grants', () => {
939939
});
940940

941941
it('refuses non-user recipient types instead of persisting rows no gate reads', async () => {
942+
// Every non-`user` member of RecordShareRecipientType — no `as any`
943+
// needed since #4539 aligned the contract type to the storage select.
942944
for (const recipientType of ['group', 'position', 'unit_and_subordinates', 'guest'] as const) {
943945
await expect(
944946
svc.grant(
945-
{ object: 'account', recordId: 'a1', recipientId: 'g1', recipientType: recipientType as any },
947+
{ object: 'account', recordId: 'a1', recipientId: 'g1', recipientType },
946948
{ userId: 'alice' },
947949
),
948950
).rejects.toThrow(/VALIDATION_FAILED/);

packages/services/service-datasource/src/external-datasource-service.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import type {
2525
} from '@objectstack/spec/contracts';
2626
import type { SchemaDiffEntry } from '@objectstack/spec/shared';
2727
import {
28-
suggestFieldType,
28+
suggestFieldTypeForSqlType,
2929
isCompatible,
3030
ExternalCatalogSchema,
3131
type ExternalCatalog,
@@ -211,7 +211,7 @@ export class ExternalDatasourceService implements IExternalDatasourceService {
211211
if (exclude.has(col.name)) continue;
212212

213213
const fieldName = opts.rename?.[col.name] ?? col.name;
214-
const suggested = suggestFieldType(col.type, dialect);
214+
const suggested = suggestFieldTypeForSqlType(col.type, dialect);
215215
const fieldType: FieldType = suggested ?? 'text';
216216
if (!suggested) {
217217
review.push({
@@ -310,7 +310,7 @@ export class ExternalDatasourceService implements IExternalDatasourceService {
310310
sqlType: c.type,
311311
nullable: c.nullable,
312312
primaryKey: c.primaryKey,
313-
suggestedFieldType: suggestFieldType(c.type, schema.dialect as SqlDialect),
313+
suggestedFieldType: suggestFieldTypeForSqlType(c.type, schema.dialect as SqlDialect),
314314
})),
315315
};
316316
}),

packages/spec/api-surface.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@
709709
"resolveSearchFields (function)",
710710
"sequenceWidth (function)",
711711
"stripLegacyApiMethods (function)",
712-
"suggestFieldType (function)",
712+
"suggestFieldTypeForSqlType (function)",
713713
"utcInstantMs (function)",
714714
"validateDriverConfig (function)",
715715
"valueSchemaFor (function)"
@@ -3840,6 +3840,7 @@
38403840
"RealtimeSubscriptionFilter (interface)",
38413841
"RealtimeSubscriptionOptions (interface)",
38423842
"RecordShare (interface)",
3843+
"RecordShareRecipientType (type)",
38433844
"RemoteTable (interface)",
38443845
"ReportFormat (type)",
38453846
"ReportQuery (interface)",
@@ -3877,7 +3878,6 @@
38773878
"ShareLinkAudience (type)",
38783879
"ShareLinkExecutionContext (interface)",
38793880
"ShareLinkPermission (type)",
3880-
"ShareRecipientType (type)",
38813881
"ShareSource (type)",
38823882
"SharingExecutionContext (interface)",
38833883
"SharingRuleEvaluationResult (interface)",
@@ -4455,6 +4455,8 @@
44554455
"F (const)",
44564456
"FieldMapping (type)",
44574457
"FieldMappingSchema (const)",
4458+
"FieldMappingTransform (type)",
4459+
"FieldMappingTransformSchema (const)",
44584460
"FieldName (type)",
44594461
"FieldNameSchema (const)",
44604462
"FlowName (type)",
@@ -4507,8 +4509,6 @@
45074509
"SystemIdentifierSchema (const)",
45084510
"TemplateExpressionInput (type)",
45094511
"TemplateExpressionInputSchema (const)",
4510-
"TransformType (type)",
4511-
"TransformTypeSchema (const)",
45124512
"VISIBILITY_ALIAS_KEYS (const)",
45134513
"ViewName (type)",
45144514
"ViewNameSchema (const)",

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,11 @@
3030
"RetryPolicySchema — [./automation (const)] ≠ [./system (const)]",
3131
"Session — [./api (type)] ≠ [./identity (type)]",
3232
"SessionSchema — [./api (const)] ≠ [./identity (const)]",
33-
"ShareRecipientType — [./contracts (type)] ≠ [./security (const)]",
3433
"TenantPlan — [./cloud (type)] ≠ [./system (type)]",
3534
"TenantPlanSchema — [./cloud (const)] ≠ [./system (const)]",
36-
"TransformType — [./data (const)] ≠ [./shared (type)]",
3735
"WebhookConfig — [./api (type)] ≠ [./integration (type)]",
3836
"WebhookConfigSchema — [./api (const)] ≠ [./integration (const)]",
3937
"WebhookEvent — [./api (type)] ≠ [./integration (type)]",
40-
"WebhookEventSchema — [./api (const)] ≠ [./integration (const)]",
41-
"suggestFieldType — [., ./shared (function)] ≠ [./data (function)]"
38+
"WebhookEventSchema — [./api (const)] ≠ [./integration (const)]"
4239
]
4340
}

packages/spec/json-schema.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1137,6 +1137,7 @@
11371137
"shared/ExpressionInput",
11381138
"shared/ExpressionMeta",
11391139
"shared/FieldMapping",
1140+
"shared/FieldMappingTransform",
11401141
"shared/FieldName",
11411142
"shared/FlowName",
11421143
"shared/HttpMethod",
@@ -1156,7 +1157,6 @@
11561157
"shared/StaticMount",
11571158
"shared/SystemIdentifier",
11581159
"shared/TemplateExpressionInput",
1159-
"shared/TransformType",
11601160
"shared/ViewName",
11611161
"studio/ActionContribution",
11621162
"studio/ActionLocation",

0 commit comments

Comments
 (0)