Skip to content

Commit 35137b9

Browse files
committed
chore(spec)!: retire IDataEngine.batch? per ADR-0119 D3 (#4618)
`batch?` was declared on `IDataEngine` for as long as that contract existed and was never implemented by any engine — ObjectQL has no `batch` method and there is no other engine in the tree — and never called: `DataEngineRequest` was imported by exactly one file, the contract declaring the member. Its whole specification was a three-word doc comment, which settles nothing about partial failure, ordering, cross-object references, rollback scope, or what `transaction: false` meant. Its neighbours `getDefaultDriverName?` / `getDriverByName?` earn their optionality by naming an implementer and a probing caller; this one named nothing. The tell that nobody designed against it: `DataEngineBatchRequestSchema` nested the request union recursively — a batch could contain batches — with no statement about what that meant. The only test was a type pin asserting the property was defined, which could not fail while the declaration existed. What it claimed is now covered by members that are real: ADR-0119 D1 made `transaction` reachable through the contract, D4 made `batchData`'s `atomic` honest, and the wire batch has always gone through `POST {basePath}/batch`. So this deletes a false affordance, not a capability (ADR-0049). - Remove the member and the `DataEngineRequest` import from the contract. - Remove `DataEngineBatchRequestSchema` and its arm from the request union. Keep the union itself: every remaining arm is now unread too, but retiring a published wire protocol is a different decision, tracked separately. - Drop the type-pin test and the schema's self-parsing suite. - Registry entry `data-engine-batch-retired` carries the FROM → TO for the upgrade guide and spec-changes.json. Deliberately no `retiredKey()` tombstone: a tombstone delivers through a parse, and nothing ever parsed this schema. Its three authorable-surface baseline lines, its json-schema.manifest entry and the stale docs-import-surface baseline line are dropped here, deliberately. The enforced channel is tsc. Refs: ADR-0119 D3, ADR-0049, ADR-0078, #4618, #4612 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NKcGqCYCCpMkB5UW8jNPXx
1 parent 65ca83a commit 35137b9

13 files changed

Lines changed: 140 additions & 178 deletions
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
"@objectstack/spec": major
3+
---
4+
5+
chore(spec)!: retire `IDataEngine.batch?` — declared for the life of the contract, implemented by nothing, called by no one (ADR-0119 D3, #4618)
6+
7+
**FROM → TO**
8+
9+
| Removed | Use instead |
10+
|---|---|
11+
| `IDataEngine.batch?(requests, { transaction })` | `IObjectQLEngine.transaction(cb)` for in-process multi-write atomicity |
12+
| — a batch over ONE object | the metadata protocol's `batchData` with `options.atomic: true` |
13+
| — a cross-object batch over the wire | `POST {basePath}/batch` |
14+
| `DataEngineBatchRequestSchema` / `data/DataEngineBatchRequest` JSON schema | nothing — it described only the removed member |
15+
16+
**One-line fix:** delete the `batch` implementation from any engine that has one (there were none in this repo) and route multi-write atomicity through `engine.transaction(cb)`.
17+
18+
## Why
19+
20+
`batch?` was declared on `IDataEngine` for as long as that contract has existed and was **never implemented by any engine**`ObjectQL` has no `batch` method, and there is no other engine in the tree. It also had **no caller**: `DataEngineRequest` was imported by exactly one file, the contract declaring the member.
21+
22+
Its entire specification was a three-word doc comment, "Batch Operations (Transactional)", which settles nothing about partial failure, ordering, cross-object references, rollback scope, or what `transaction: false` was supposed to mean — the questions a batch API exists to answer. Contrast its neighbours `getDefaultDriverName?` / `getDriverByName?`, whose optionality is evidenced: each names its implementer and its probing caller.
23+
24+
The tell that nobody ever designed against it is in the schema. `DataEngineBatchRequestSchema.requests` nested the request union **recursively** — a batch could contain batches — with no statement anywhere about what that meant for ordering or rollback.
25+
26+
The only test was a type pin: an ad-hoc object literal carrying a `batch` property, asserting the property was defined. It could not fail while the declaration existed, and would have passed unchanged for the member's entire life with no engine implementing it. A test that asserts a contract member is *declared* is not evidence the contract is *honoured*.
27+
28+
A declared capability that cannot be exercised is ADR-0049's enforce-or-remove target. What this one claimed is now covered by members that are real — ADR-0119 D1 made `transaction` reachable through the contract, D4 made `batchData`'s `atomic` honest — so the removal deletes a false affordance, not a capability.
29+
30+
## Scope notes
31+
32+
- **The wire batch is untouched.** `POST {basePath}/batch` validates with `CrossObjectBatchRequestSchema` / `BatchUpdateRequestSchema` from `api/batch.zod.ts` — a different schema that never had anything to do with the removed one.
33+
- **`DataEngineRequestSchema` stays**, minus its `batch` arm. Every remaining arm now has zero readers in this repo (there is no Virtual Data Engine implementation, only this schema describing one), which makes the whole block a further enforce-or-remove candidate — tracked separately, because retiring a published wire protocol is a different decision from retiring `batch?` and does not belong in a change whose title promised something narrower.
34+
- **Deliberately no `retiredKey()` tombstone.** A tombstone delivers its prescription through a *parse*, and nothing ever parsed `DataEngineBatchRequestSchema`. A prescription nobody can receive is noise (the `spec-property-retirement` playbook's third route). Its three `authorable-surface.json` baseline lines and its `json-schema.manifest.json` entry are therefore dropped in this change, deliberately, along with the now-stale `docs-import-surface.baseline.json` line that excused its missing type export. The enforced channel here is `tsc`, and it points at callers.

content/docs/references/data/data-engine.mdx

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ The Data Engine acts as the "Driver" layer in the Hexagonal Architecture.
2424
## TypeScript Usage
2525

2626
```typescript
27-
import { BaseEngineOptionsSchema, DataEngineAggregateOptionsSchema, DataEngineAggregateRequestSchema, DataEngineBatchRequestSchema, DataEngineCountOptionsSchema, DataEngineCountRequestSchema, DataEngineDeleteOptionsSchema, DataEngineDeleteRequestSchema, DataEngineExecuteRequestSchema, DataEngineFilterSchema, DataEngineFindOneRequestSchema, DataEngineFindRequestSchema, DataEngineInsertOptionsSchema, DataEngineInsertRequestSchema, DataEngineQueryOptionsSchema, DataEngineRequestSchema, DataEngineSortSchema, DataEngineUpdateOptionsSchema, DataEngineUpdateRequestSchema, DataEngineVectorFindRequestSchema, DroppedFieldsEventSchema, EngineAggregateOptionsSchema, EngineCountOptionsSchema, EngineDeleteOptionsSchema, EngineQueryOptionsSchema, EngineUpdateOptionsSchema } from '@objectstack/spec/data';
27+
import { BaseEngineOptionsSchema, DataEngineAggregateOptionsSchema, DataEngineAggregateRequestSchema, DataEngineCountOptionsSchema, DataEngineCountRequestSchema, DataEngineDeleteOptionsSchema, DataEngineDeleteRequestSchema, DataEngineExecuteRequestSchema, DataEngineFilterSchema, DataEngineFindOneRequestSchema, DataEngineFindRequestSchema, DataEngineInsertOptionsSchema, DataEngineInsertRequestSchema, DataEngineQueryOptionsSchema, DataEngineRequestSchema, DataEngineSortSchema, DataEngineUpdateOptionsSchema, DataEngineUpdateRequestSchema, DataEngineVectorFindRequestSchema, DroppedFieldsEventSchema, EngineAggregateOptionsSchema, EngineCountOptionsSchema, EngineDeleteOptionsSchema, EngineQueryOptionsSchema, EngineUpdateOptionsSchema } from '@objectstack/spec/data';
2828
import type { BaseEngineOptions, DataEngineAggregateOptions, DataEngineCountOptions, DataEngineDeleteOptions, DataEngineFilter, DataEngineInsertOptions, DataEngineQueryOptions, DataEngineRequest, DataEngineSort, DataEngineUpdateOptions, DroppedFieldsEvent, EngineAggregateOptions, EngineCountOptions, EngineDeleteOptions, EngineQueryOptions, EngineUpdateOptions } from '@objectstack/spec/data';
2929

3030
// Validate data
@@ -71,19 +71,6 @@ Options for DataEngine.aggregate operations
7171
| **query** | `{ context?: object; where?: Record<string, any> \| any; groupBy?: string[]; aggregations?: { function: Enum<'count' \| 'sum' \| 'avg' \| 'min' \| 'max' \| 'count_distinct' \| 'array_agg' \| 'string_agg'>; field?: string; alias: string; distinct?: boolean; … }[]; … }` || |
7272

7373

74-
---
75-
76-
## DataEngineBatchRequest
77-
78-
### Properties
79-
80-
| Property | Type | Required | Description |
81-
| :--- | :--- | :--- | :--- |
82-
| **method** | `'batch'` || |
83-
| **requests** | `{ method: 'find'; object: string; query?: object } \| { method: 'findOne'; object: string; query?: object } \| { method: 'insert'; object: string; data: Record<string, any> \| Record<string, any>[]; options?: object } \| { method: 'update'; object: string; data: Record<string, any>; id?: string \| number; … } \| { method: 'delete'; object: string; id?: string \| number; options?: object } \| { method: 'count'; object: string; query?: object } \| { method: 'aggregate'; object: string; query: object } \| { method: 'execute'; command: any; options?: Record<string, any> } \| { method: 'vectorFind'; object: string; vector: number[]; where?: Record<string, any> \| any; … }[]` || |
84-
| **transaction** | `boolean` | optional | |
85-
86-
8774
---
8875

8976
## DataEngineCountOptions
@@ -352,18 +339,6 @@ This schema accepts one of the following structures:
352339

353340
### Properties
354341

355-
| Property | Type | Required | Description |
356-
| :--- | :--- | :--- | :--- |
357-
| **method** | `'batch'` || |
358-
| **requests** | `{ method: 'find'; object: string; query?: object } \| { method: 'findOne'; object: string; query?: object } \| { method: 'insert'; object: string; data: Record<string, any> \| Record<string, any>[]; options?: object } \| { method: 'update'; object: string; data: Record<string, any>; id?: string \| number; … } \| { method: 'delete'; object: string; id?: string \| number; options?: object } \| { method: 'count'; object: string; query?: object } \| { method: 'aggregate'; object: string; query: object } \| { method: 'execute'; command: any; options?: Record<string, any> } \| { method: 'vectorFind'; object: string; vector: number[]; where?: Record<string, any> \| any; … }[]` || |
359-
| **transaction** | `boolean` | optional | |
360-
361-
---
362-
363-
#### Option 9
364-
365-
### Properties
366-
367342
| Property | Type | Required | Description |
368343
| :--- | :--- | :--- | :--- |
369344
| **method** | `'execute'` || |
@@ -372,7 +347,7 @@ This schema accepts one of the following structures:
372347

373348
---
374349

375-
#### Option 10
350+
#### Option 9
376351

377352
### Properties
378353

docs/protocol-upgrade-guide.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,9 @@ Finally, five keys retire because the advisory lint could never have warned abou
241241
- **`data-driver-find-stream-retired`**`contracts.IDataDriver.findStream / data.DriverInterfaceSchema.findStream` → find() with limit/offset — the paged read whose determinism IS enforced (IDataDriver.find, data/pagination-conformance.ts)
242242
- Why not automatic: `findStream` was a REQUIRED contract method documented as "optimized for large datasets to avoid memory overflow", and in two of its three implementations it delivered the opposite: `SqlDriver` and `InMemoryDriver` both awaited `find()` for the ENTIRE result set and then yielded it row by row, so the peak memory a caller was promised protection from was already reached before the first yield. The third (`MongoDBDriver._findStream`) did walk a cursor, but it was the one read path in that driver never routed through `buildFindOptions`, so it hardcoded `projection: { _id: 0 }` and silently discarded `query.fields`. None of it was ever observed, because the method had NO caller in either repository: the engine exposes no stream entry, and the REST export, import and bulk-read paths all go through `find()`. The ~20 driver test doubles that existed only to satisfy a required method almost all threw `not implemented`, and nothing ever noticed — which is the proof, not the anecdote. Being REQUIRED, it also taxed every new driver and every test double with an implementation of a capability the platform does not have. Rather than build a caller to justify three implementations, the method is retired; a real cursor-based read should return WITH the caller that needs it (ADR-0049 enforce-or-remove). This is a TS/API contract surface — a driver is CODE, never stack metadata — so there is no source for the chain to rewrite, and deliberately no schema tombstone either: nothing ever ran a driver object through `DriverInterfaceSchema.parse()`, so a prescription there would have no one to reach. The enforced channel is tsc, and it points at callers. ADR-0049 / ADR-0078, #4484.
243243
- Done when: No code calls `driver.findStream(...)`; large reads page through `find()` with `limit`/`offset` (which guarantees a total order across the whole walk) or go through the export surface. Drivers and test doubles no longer implement the method — one left behind still compiles and is simply never reached, so removing it is cleanup rather than a break, while a CALLER of it no longer type-checks.
244+
- **`data-engine-batch-retired`**`contracts.IDataEngine.batch / data.DataEngineBatchRequestSchema``IObjectQLEngine.transaction(cb)` for in-process multi-write atomicity; the metadata protocol's `batchData` with `options.atomic: true` for a batch over one object; `POST {basePath}/batch` on the wire
245+
- Why not automatic: `batch?` was declared on `IDataEngine` for as long as that contract existed and was never implemented by any engine: `ObjectQL` has no `batch` method and there is no other engine in the tree. It also had no caller — `DataEngineRequest` was imported by exactly one file, the contract declaring the member. Its entire specification was a three-word doc comment ("Batch Operations (Transactional)"), which settles nothing about partial failure, ordering, cross-object references, rollback scope, or what `transaction: false` was supposed to mean — the questions a batch API exists to answer. Contrast its neighbours `getDefaultDriverName?` / `getDriverByName?`, whose optionality is evidenced: each names its implementer and its probing caller. The tell that nobody ever designed against it is in the schema: `DataEngineBatchRequestSchema.requests` nested the request union RECURSIVELY, so a batch could contain batches, with no statement anywhere about what that meant for ordering or rollback. The only test was a type pin — an ad-hoc object literal carrying a `batch` property, asserting the property was defined — which could not fail while the declaration existed and would have passed unchanged for the member's whole life with no engine implementing it. What it claimed is now covered by members that are real, so the removal deletes a false affordance rather than a capability: ADR-0119 D1 made `transaction` reachable through the contract and D4 made `batchData`'s `atomic` honest, while the wire batch has always validated with `CrossObjectBatchRequestSchema` / `BatchUpdateRequestSchema` from `api/batch.zod.ts` — a different schema entirely, untouched here. TS/API surfaces only: an engine is CODE, never stack metadata, so there is no source for the chain to rewrite. Deliberately no schema tombstone either — nothing ever parsed `DataEngineBatchRequestSchema`, so a `retiredKey()` prescription would have no one to reach; its three `authorable-surface.json` baseline lines and its `json-schema.manifest.json` entry are dropped in the same change, deliberately. The enforced channel is tsc. ADR-0049 / ADR-0078, #4618.
246+
- Done when: No code calls `engine.batch(...)` and no type references `DataEngineBatchRequest`; in-process multi-write atomicity goes through `IObjectQLEngine.transaction(cb)`, a batch over one object through `batchData` with `options.atomic: true`, and a cross-object batch over the wire through `POST {basePath}/batch`. Because no engine implemented the member, an implementation left behind still compiles and is simply never reached; a CALLER of it no longer type-checks — and there were none.
244247

245248
---
246249

packages/spec/api-surface.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,6 @@
261261
"DataEngineAggregateOptions (type)",
262262
"DataEngineAggregateOptionsSchema (const)",
263263
"DataEngineAggregateRequestSchema (const)",
264-
"DataEngineBatchRequestSchema (const)",
265264
"DataEngineContractSchema (const)",
266265
"DataEngineCountOptions (type)",
267266
"DataEngineCountOptionsSchema (const)",

packages/spec/authorable-surface.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3162,9 +3162,6 @@
31623162
"data/DataEngineAggregateRequest:method",
31633163
"data/DataEngineAggregateRequest:object",
31643164
"data/DataEngineAggregateRequest:query",
3165-
"data/DataEngineBatchRequest:method",
3166-
"data/DataEngineBatchRequest:requests",
3167-
"data/DataEngineBatchRequest:transaction",
31683165
"data/DataEngineCountOptions:context",
31693166
"data/DataEngineCountOptions:filter",
31703167
"data/DataEngineCountRequest:method",

packages/spec/docs-import-surface.baseline.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
"data/ClockTimeValue — no type export",
3434
"data/ContextTokenPlaceholder — no type export",
3535
"data/DataEngineAggregateRequest — no type export",
36-
"data/DataEngineBatchRequest — no type export",
3736
"data/DataEngineCountRequest — no type export",
3837
"data/DataEngineDeleteRequest — no type export",
3938
"data/DataEngineExecuteRequest — no type export",

packages/spec/json-schema.manifest.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,6 @@
699699
"data/CurrencyValue",
700700
"data/DataEngineAggregateOptions",
701701
"data/DataEngineAggregateRequest",
702-
"data/DataEngineBatchRequest",
703702
"data/DataEngineCountOptions",
704703
"data/DataEngineCountRequest",
705704
"data/DataEngineDeleteOptions",

0 commit comments

Comments
 (0)