Skip to content

Commit 3c7bcc0

Browse files
os-zhuangclaude
andauthored
feat(spec)!: converge the 11 contracts-vs-domain dual-source type names (#4538) (#4568)
Each of the 11 baseline rows judged individually against a three-repo import-level scan (framework, cloud, objectui): - Converged onto the domain zod declaration, re-exported from ./contracts: NotificationChannel (system), ValidationResult / HealthStatus / StartupOptions / PluginStartupResult (kernel), JobExecution (system, after renaming the schema's dead `duration` to the runtime-true `durationMs`), AnalyticsQuery (data, after the schema dropped the `timezone` .default('UTC') the /analytics entry always refused to apply - absence means the engine resolves org timezone, #1982/#2018). IStartupOrchestrator.orchestrateStartup now takes StartupOptionsInput. - Renamed: contracts DriverCapabilities -> AnalyticsDriverCapabilities (two live concepts shared one name; the data domain's driver feature-flag record keeps it). @objectstack/service-analytics re-export renamed in lockstep. - Removed dead domain-side declarations (zero consumers in all three repos; last of the #4411 family): system MetadataExportOptions(Schema) / MetadataImportOptions(Schema) - the contracts IMetadataService parameter interfaces own the names now - and the system `JobSchedule = Schedule` back-compat alias (authoring tier keeps its real name, Schedule). @objectstack/metadata now re-exports the two Metadata*Options names from ./contracts, the shape its own manager implements. All 11 dual-source-exports.baseline.json rows deleted; baselines (api-surface, authorable-surface, json-schema.manifest, reference docs) regenerated via check:generated --fix; the authorable-surface lines for the removed runtime option bags deleted by hand per the #4458 precedent (not authorable metadata - no tombstone, no D2 conversion). Out-of-scope finding filed unassigned as #4567 (defineJob cron envelope vs croner). Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0222d3c commit 3c7bcc0

30 files changed

Lines changed: 304 additions & 362 deletions
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
"@objectstack/spec": major
3+
"@objectstack/service-analytics": major
4+
"@objectstack/metadata": patch
5+
---
6+
7+
feat(spec)!: converge the 11 contracts-vs-domain dual-source type names (#4538)
8+
9+
`packages/spec/src/contracts/` hand-wrote parameter/result interfaces whose
10+
names collided with same-named zod-derived types in the domains — the #4411
11+
trap, tracked as 11 rows of `dual-source-exports.baseline.json`. Each name was
12+
judged individually against a three-repo import-level scan (framework, cloud,
13+
objectui): which declaration actually flows at runtime decides the direction.
14+
All 11 rows are deleted from the baseline; no name below is exported twice
15+
anymore.
16+
17+
**Converged — `./contracts` now re-exports the domain zod type (same
18+
declaration on both entries, imports keep compiling from either):**
19+
20+
- `NotificationChannel``system/notification.zod`'s
21+
`z.infer<NotificationChannelSchema>` (member sets were identical).
22+
- `ValidationResult``kernel/plugin-validator.zod` (shapes were identical).
23+
- `HealthStatus``kernel/startup-orchestrator.zod` (`details` narrows
24+
`Record<string, any>``Record<string, unknown>`).
25+
- `PluginStartupResult``kernel/startup-orchestrator.zod`. FROM `plugin:
26+
Plugin` (live object) and `error?: Error` TO the serializable projection
27+
(`plugin: { name, version? }`-passthrough, `error?: { name, message,
28+
stack?, code? }`). Neither side had any consumer outside spec; the
29+
zod-validatable shape wins.
30+
- `StartupOptions``kernel/startup-orchestrator.zod` — the PARSED tier
31+
(defaults applied). `IStartupOrchestrator.orchestrateStartup` now takes
32+
`StartupOptionsInput` (the caller-authored all-optional tier, also
33+
re-exported from `./contracts`). Fix for callers typed to the old
34+
all-optional `StartupOptions`: rename to `StartupOptionsInput`.
35+
- `JobExecution``system/job.zod`. The system schema's `duration` field is
36+
RENAMED `durationMs` — that is what every job adapter produces and what the
37+
`sys_job_run.duration_ms` column round-trips; the schema described records
38+
nothing ever wrote. Fix: `duration``durationMs` when parsing
39+
`JobExecutionSchema` payloads.
40+
- `AnalyticsQuery``data/analytics.zod`. The domain schema aligned to the
41+
contract's semantics first: `timezone` LOST its `.default('UTC')` — absence
42+
is meaningful (the engine resolves org timezone, #1982/#2018; the
43+
`/analytics` entry always refused to apply that default). The schema is now
44+
transform-free, so `AnalyticsQuery``AnalyticsQueryInput` (both kept
45+
exported). Fix for code that relied on `.parse()` injecting `timezone:
46+
'UTC'`: pass the timezone explicitly or resolve it via the engine chain
47+
(`selection.timezone ?? context.timezone ?? 'UTC'`).
48+
49+
**Renamed — two genuinely different concepts were sharing one name (both
50+
flow at runtime):**
51+
52+
- `./contracts` `DriverCapabilities`**`AnalyticsDriverCapabilities`**
53+
(`{ nativeSql, objectqlAggregate, inMemory }`, the analytics strategy-chain
54+
execution-path probe). The `DriverCapabilities` name now belongs solely to
55+
the data domain's driver feature-flag record (`DriverCapabilitiesSchema`,
56+
what `IDataDriver.supports` declares). Fix: importers of the trio from
57+
`@objectstack/spec/contracts` (or `@objectstack/service-analytics`, whose
58+
re-export is renamed in lockstep) rename the import; importers who meant
59+
the driver flags import `DriverCapabilities` from `@objectstack/spec/data`.
60+
61+
**Removed — the domain-side declaration was dead (zero import-level consumers
62+
in framework/cloud/objectui; the #4411 family's last survivors):**
63+
64+
- `system` `MetadataExportOptionsSchema` / `MetadataExportOptions` and
65+
`MetadataImportOptionsSchema` / `MetadataImportOptions` (the
66+
`output`/`source`-directory bags). The names now have ONE declaration each:
67+
the `IMetadataService.exportMetadata` / `importMetadata` parameter
68+
interfaces on `./contracts` (`types`/`namespaces`/`format` and
69+
`conflictResolution`/`validate`/`dryRun`), which `MetadataManager`
70+
implements. No tombstone/D2 conversion, deliberately — these are runtime
71+
option-bag types, not authorable metadata (same reasoning as #4458).
72+
`@objectstack/metadata` re-exports the two names from `./contracts` now
73+
(it previously re-exported the dead system-side shapes its own manager
74+
did not accept).
75+
- `system` `JobSchedule` (the `= Schedule` back-compat alias). The name's one
76+
declaration is the `IJobService.schedule` boundary shape on `./contracts`
77+
(plain-string cron `expression`); the authored metadata type keeps its real
78+
name `Schedule`. Fix: `import type { JobSchedule } from
79+
'@objectstack/spec/system'``Schedule` (authoring tier) or the
80+
`./contracts` `JobSchedule` (service boundary), whichever you meant.

content/docs/references/api/analytics.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ const result = AnalyticsEndpoint.parse(data);
6666
| **order** | `Record<string, Enum<'asc' \| 'desc'>>` | optional | |
6767
| **limit** | `number` | optional | |
6868
| **offset** | `number` | optional | |
69-
| **timezone** | `string` | | |
69+
| **timezone** | `string` | optional | |
7070
| **query** | `any` | optional | [REMOVED] `query` was removed from AnalyticsQueryRequest in @objectstack/spec 17.0.0 (#3878). The `{ cube, query: {...}` } envelope was the dialect of the retired degraded analytics shim (#3891) — the real engine never understood it. Move the query.* fields to the body top level: `{ cube, measures, dimensions?, where?, timeDimensions?, order?, limit?, offset?, timezone? }`. |
7171
| **format** | `any` | optional | [REMOVED] `format` was removed from AnalyticsQueryRequest in @objectstack/spec 17.0.0 (#3878). It was never implemented — every response is the JSON envelope. Delete the key; for CSV/XLSX use the export surface instead. |
7272

content/docs/references/data/analytics.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ const result = AggregationMetricType.parse(data);
6262
| **order** | `Record<string, Enum<'asc' \| 'desc'>>` | optional | |
6363
| **limit** | `number` | optional | |
6464
| **offset** | `number` | optional | |
65-
| **timezone** | `string` | | |
65+
| **timezone** | `string` | optional | |
6666

6767

6868
---

content/docs/references/system/job.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ const result = CronSchedule.parse(data);
8787
| **completedAt** | `string` | optional | ISO 8601 datetime when execution completed |
8888
| **status** | `Enum<'running' \| 'success' \| 'failed' \| 'timeout'>` || Execution status |
8989
| **error** | `string` | optional | Error message if failed |
90-
| **duration** | `integer` | optional | Execution duration in milliseconds |
90+
| **durationMs** | `integer` | optional | Execution duration in milliseconds |
9191

9292

9393
---

content/docs/references/system/metadata-persistence.mdx

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ Defines the lifecycle and mutability of a metadata item.
1616
## TypeScript Usage
1717

1818
```typescript
19-
import { MetadataCollectionInfo, MetadataDiffResult, MetadataExportOptions, MetadataHistoryQueryOptions, MetadataHistoryQueryResult, MetadataHistoryRecord, MetadataHistoryRetentionPolicy, MetadataImportOptions, MetadataLoadOptions, MetadataLoadResult, MetadataLoaderContract, MetadataRecord, MetadataSaveOptions, MetadataSaveResult, MetadataScope, MetadataSource, MetadataState, MetadataStats, MetadataWatchEvent, PackagePublishResult } from '@objectstack/spec/system';
20-
import type { MetadataCollectionInfo, MetadataDiffResult, MetadataExportOptions, MetadataHistoryQueryOptions, MetadataHistoryQueryResult, MetadataHistoryRecord, MetadataHistoryRetentionPolicy, MetadataImportOptions, MetadataLoadOptions, MetadataLoadResult, MetadataLoaderContract, MetadataRecord, MetadataSaveOptions, MetadataSaveResult, MetadataScope, MetadataSource, MetadataState, MetadataStats, MetadataWatchEvent, PackagePublishResult } from '@objectstack/spec/system';
19+
import { MetadataCollectionInfo, MetadataDiffResult, MetadataHistoryQueryOptions, MetadataHistoryQueryResult, MetadataHistoryRecord, MetadataHistoryRetentionPolicy, MetadataLoadOptions, MetadataLoadResult, MetadataLoaderContract, MetadataRecord, MetadataSaveOptions, MetadataSaveResult, MetadataScope, MetadataSource, MetadataState, MetadataStats, MetadataWatchEvent, PackagePublishResult } from '@objectstack/spec/system';
20+
import type { MetadataCollectionInfo, MetadataDiffResult, MetadataHistoryQueryOptions, MetadataHistoryQueryResult, MetadataHistoryRecord, MetadataHistoryRetentionPolicy, MetadataLoadOptions, MetadataLoadResult, MetadataLoaderContract, MetadataRecord, MetadataSaveOptions, MetadataSaveResult, MetadataScope, MetadataSource, MetadataState, MetadataStats, MetadataWatchEvent, PackagePublishResult } from '@objectstack/spec/system';
2121

2222
// Validate data
2323
const result = MetadataCollectionInfo.parse(data);
@@ -55,20 +55,6 @@ const result = MetadataCollectionInfo.parse(data);
5555
| **summary** | `string` | optional | Human-readable summary of changes |
5656

5757

58-
---
59-
60-
## MetadataExportOptions
61-
62-
### Properties
63-
64-
| Property | Type | Required | Description |
65-
| :--- | :--- | :--- | :--- |
66-
| **types** | `string[]` | optional | |
67-
| **namespaces** | `string[]` | optional | |
68-
| **output** | `string` || Output directory or file |
69-
| **format** | `Enum<'yaml' \| 'json' \| 'typescript' \| 'javascript'>` || Metadata file format |
70-
71-
7258
---
7359

7460
## MetadataHistoryQueryOptions
@@ -135,19 +121,6 @@ const result = MetadataCollectionInfo.parse(data);
135121
| **cleanupIntervalHours** | `integer` || How often to run cleanup (in hours) |
136122

137123

138-
---
139-
140-
## MetadataImportOptions
141-
142-
### Properties
143-
144-
| Property | Type | Required | Description |
145-
| :--- | :--- | :--- | :--- |
146-
| **source** | `string` || Input directory or file |
147-
| **strategy** | `Enum<'merge' \| 'replace' \| 'skip'>` || |
148-
| **validate** | `boolean` || |
149-
150-
151124
---
152125

153126
## MetadataLoadOptions

packages/metadata/src/index.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ export type {
4545
MetadataStats,
4646
MetadataLoadOptions,
4747
MetadataSaveOptions,
48-
MetadataExportOptions,
49-
MetadataImportOptions,
5048
MetadataLoadResult,
5149
MetadataSaveResult,
5250
MetadataWatchEvent,
@@ -60,11 +58,18 @@ export type {
6058
MetadataHistoryRetentionPolicy,
6159
} from '@objectstack/spec/system';
6260

63-
// Re-export IMetadataService contract
61+
// Re-export IMetadataService contract.
62+
// [#4538] `MetadataExportOptions` / `MetadataImportOptions` moved into this
63+
// block: this package used to re-export the same-named system-entry bags
64+
// (`output`/`source`-flavored, removed with #4538) while `MetadataManager`
65+
// implements the contracts shapes — the public re-export was pointing at the
66+
// wrong declaration.
6467
export type {
6568
IMetadataService,
6669
MetadataWatchCallback,
6770
MetadataWatchHandle,
71+
MetadataExportOptions,
72+
MetadataImportOptions,
6873
MetadataTypeInfo,
6974
MetadataImportResult,
7075
} from '@objectstack/spec/contracts';

packages/plugins/driver-memory/src/memory-analytics.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ import { AnalyticsQuerySchema, defineCube } from '@objectstack/spec/data';
77
import type { AnalyticsQuery, AnalyticsQueryInput, Cube } from '@objectstack/spec/data';
88

99
/**
10-
* Author-tier literal → the parsed `AnalyticsQuery` the service contract takes.
10+
* Validate a literal through the schema before handing it to the service —
11+
* the same route a real request body takes (the REST layer validates against
12+
* the schema and forwards).
1113
*
12-
* `timezone` is `.default('UTC')` on the schema, so it is optional to write and
13-
* required on the parsed type — the two tiers are genuinely different types. A
14-
* real query reaches `query()` through the schema (the REST layer parses the
15-
* request body), so these tests take the same route rather than hand-writing
16-
* the filled-in default: the parse IS the proof that the default lands. Until
17-
* #4311 no tsc read this file, so 19 author-tier literals sat unnoticed in a
18-
* parameter that had required `timezone` all along.
14+
* [#4538] The two tiers collapsed: `AnalyticsQuerySchema` no longer carries
15+
* any `.default()`/`.transform()` (`timezone` is genuinely optional — absence
16+
* means the engine resolves org timezone, #1982/#2018), so `AnalyticsQuery`
17+
* and `AnalyticsQueryInput` are the same shape and the parse is validation
18+
* only. The helper stays so every test query is proven schema-valid.
1919
*/
2020
const asQuery = (input: AnalyticsQueryInput): AnalyticsQuery => AnalyticsQuerySchema.parse(input);
2121

packages/runtime/src/domains/analytics.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,13 @@ import type { DomainHandlerDeps, DomainRoute } from '../domain-handler-registry.
3838
* "unrecognized key" — gets a bespoke hint at the contract field `where`.
3939
*
4040
* Validation only — the ORIGINAL body is forwarded to the service untouched.
41-
* (Parsing would inject the schema's `timezone: 'UTC'` default and silently
42-
* override the engine's org-timezone resolution, #1982/#2018.)
41+
* (Historically load-bearing: the schema carried a `timezone: 'UTC'` default
42+
* that parsing would have injected, silently overriding the engine's
43+
* org-timezone resolution, #1982/#2018. #4538 removed that default from
44+
* `AnalyticsQuerySchema` itself — the schema is transform-free now, so
45+
* validated body ≡ parsed output by construction — but forwarding the
46+
* original body stays the rule: it keeps this entry immune to any future
47+
* default someone adds to the schema without re-reading this file.)
4348
*/
4449
function assertAnalyticsQueryBody(body: unknown): void {
4550
if (body && typeof body === 'object' && !Array.isArray(body)) {

packages/services/service-analytics/src/__tests__/analytics-service.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { AnalyticsService } from '../analytics-service.js';
77
import { CubeRegistry } from '../cube-registry.js';
88
import { NativeSQLStrategy } from '../strategies/native-sql-strategy.js';
99
import { ObjectQLStrategy } from '../strategies/objectql-strategy.js';
10-
import type { DriverCapabilities } from '../strategies/types.js';
10+
import type { AnalyticsDriverCapabilities } from '../strategies/types.js';
1111

1212
// ─────────────────────────────────────────────────────────────────
1313
// Test fixtures

packages/services/service-analytics/src/analytics-service.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type { Dataset } from '@objectstack/spec/ui';
1313
import type { Logger } from '@objectstack/spec/contracts';
1414
import { createLogger, bucketKeyToCalendarRange, zonedDateStartToUtcMs } from '@objectstack/core';
1515
import { CubeRegistry } from './cube-registry.js';
16-
import type { AnalyticsStrategy, DriverCapabilities, StrategyContext } from './strategies/types.js';
16+
import type { AnalyticsStrategy, AnalyticsDriverCapabilities, StrategyContext } from './strategies/types.js';
1717
import { NativeSQLStrategy } from './strategies/native-sql-strategy.js';
1818
import { ObjectQLStrategy } from './strategies/objectql-strategy.js';
1919
import { compileDataset, type CompiledDataset, type RelationshipResolver } from './dataset-compiler.js';
@@ -112,7 +112,7 @@ export interface AnalyticsServiceConfig {
112112
* Probe driver capabilities for the object that backs a cube.
113113
* The service calls this function to decide which strategy can handle a query.
114114
*/
115-
queryCapabilities?: (cubeName: string) => DriverCapabilities;
115+
queryCapabilities?: (cubeName: string) => AnalyticsDriverCapabilities;
116116
/**
117117
* Execute raw SQL on the driver for a given object.
118118
* Required for NativeSQLStrategy.
@@ -287,7 +287,7 @@ export interface AnalyticsServiceConfig {
287287
/**
288288
* Default capabilities when probing is not configured — assumes in-memory only.
289289
*/
290-
const DEFAULT_CAPABILITIES: DriverCapabilities = {
290+
const DEFAULT_CAPABILITIES: AnalyticsDriverCapabilities = {
291291
nativeSql: false,
292292
objectqlAggregate: false,
293293
inMemory: true,

0 commit comments

Comments
 (0)