Skip to content

Commit d359857

Browse files
feat(driver-odata): an OData v2 dialect beside v4, selected with version: '2.0' (#28177)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DVZtFRFfhUNep8fsh1opTud)_ domain:server#4 · os-elon-musk · session_016WV4cykf9LVyVYxnw1NL95 Fixes #28125 Clause-②: yes (widening) Metadata-protocol: added ## 维护者速读 - 改了什么:`@objectstack/driver-odata` 在 v4 留好的方言接缝上新增 OData v2 方言,插件配置 `version: '2.0'` 即可接 v2 服务(默认仍是 `4.0`)。覆盖卡上每一项:`d.results` / `__count` / `__next` 响应封装、`$inlinecount`、`substringof` 与 v2 带类型字面量(`datetime'…'`、`guid'…'`、`7L`、`1.5M` 等)、键谓词、`MERGE` 写入(`POST` + `X-HTTP-Method`)、EDMX 1.0 的 Association / AssociationSet。spec 的类型矩阵补 `Edm.DateTime → datetime`、`Edm.Time → time`。 - 为什么改:v2 仍是 SAP Gateway 大部分标准服务、旧版 Dynamics CRM 和 SharePoint 2013 REST 的协议;main 上 `version: '2.0'` 直接拒绝启动。SAP 专有机制(CSRF 握手、`sap-client`、`sap:*` 注解)按 10-29 裁定没有放进来。 - 风险与回滚:v4 行为不变:v4 原有 732 个测试在 main 源码和本 PR 上都是绿的。方言接口只新增 4 个可选成员,仓库外的方言照常编译。spec 两个别名只放宽(以前是 `unknown`)。回滚即 revert。 - 先看:`packages/drivers/driver-odata/src/dialect-v2.ts`,以及 `odata-driver.ts` 里 4 个可选钩子的接入点。 - 两分钟验证:`pnpm --filter @objectstack/driver-odata exec vitest run`,预期 1383 passed(其中 v2 651 条:一致性用例集 336、插件套件 276、录制驱动 15、方言 24)。 - 需要你做的:无。欠一次契约复审(`packages/spec/src/**`),由席位安排。 ## What changes - **`packages/drivers/driver-odata/src/dialect-v2.ts`** (new): `odataV2Dialect` and `parseCsdlV2`, implementing the existing `ODataDialect`. - Requests carry `DataServiceVersion: 2.0` / `MaxDataServiceVersion: 2.0` and `Accept: application/json` (verbose JSON). - Collections are read out of `d.results` (or a 1.0 service's bare `d` array), with `__next` as the next link and the string `__count` as the count. An entity is read out of `d`, and its ETag comes from `__metadata.etag`. `__metadata` and each navigation's `{ __deferred }` stub are dropped from the row. - The count is `$inlinecount=allpages`, and `contains` is `substringof('x',P)`. - Typed literals: `datetime'…'` (only for a comparand that names its instant, spelled as its UTC wall clock), `datetimeoffset'…'`, `time'PT…'`, `guid'…'`, `7L`, `1.5M` (no exponent), `1.5d`, `1.5f`. - `keyValue` reads a composite key's typed literal back, so `TrackingId=guid'…',Leg=2L` round-trips. - Values: `/Date(ms)/` → the UTC instant, or the date under a field declared `date` (v2 has no `Edm.Date`). `Edm.Int64` / `Edm.Decimal` text → number, except an `Edm.Int64` beyond the safe range, which stays text. `Edm.Time` `PT13H20M` → `13:20:00`. Writes use the inverse forms. - A partial update is `POST` with `X-HTTP-Method: MERGE`. - EDMX 1.0 `$metadata`: a navigation's target and cardinality come from its `Association` ends. Its `ReferentialConstraint` pairs are taken only on the dependent end, as v4 writes them on that navigation. Its entity-set binding comes from the `AssociationSet`s, base types included. - ⛔ No vendor mechanics (no CSRF handshake, `sap-client` or `sap:*`). - **`dialect.ts`**: the seam gains four optional members (`readValue`, `writeValue`, `keyValue`, `updateRequest`). If a dialect leaves one out, the driver keeps v4's behaviour. `odataV4Dialect` implements none of them. - **`odata-driver.ts`**: - `DIALECTS['2.0']` is the v2 dialect. - `toRow` reads each declared property through `readValue`, and `toRemote` writes it through `writeValue`. - `update` takes its verb from `updateRequest`, and the composite key predicate is re-spelled through `keyValue`. - `registerExternalObject` records each field's declared type from the object's `fields`, the way the SQL driver learns field types, so `readValue` sees it. - **`config.ts`**: `version: z.enum(['4.0', '2.0'])`, default `4.0`. - **`dialect-v4.ts`**: pure refactor: the XML helpers are exported, and the inheritance flattening is extracted into `flattenEntityTypes` for the v2 reader. The v1/EDMX refusal message now names `version: '2.0'` and still matches the v4 suite's pin. - **`packages/spec/src/data/type-compat.ts`**: `DIALECT_ALIASES.odata` gains `edm.datetime` → `datetime` and `edm.time` → `time`, and nothing else. - **Fixtures and tests**: - `northwind-v2-metadata.xml` is Northwind's v2 shape, abridged, plus two fixture-own sets with a Guid key and a composite (Guid, Int64) key. `recorded-service-v2.ts` holds the verbose JSON recordings. Both are written from the service's shape, because the container has no network. - `dialect-v2.test.ts` has 24 tests and `odata-driver-v2.test.ts` has 15. - The in-process service (`odata-service.ts`) gains a strict `version: '2.0'` mode: EDMX 1.0, the `d` envelope, `$inlinecount` and `MERGE`. It refuses v4's `$count`, `contains`, bare temporal literals and `PATCH`. - The conformance case-sets (`odata-driver.conformance.test.ts`) and the plugin-driver kit (`…conformance-kit.test.ts`) now also run over that v2 service. The v4 describe names are unchanged; the kit's object is registered with its `fields`, as the engine registers it. - `service-datasource`'s `external-object-draft-odata-metadata.test.ts` gains a v2 describe over its own copy of the v2 `$metadata`. This test-only path was added to the claim's surface by the seat. - **Docs**: `content/docs/data-modeling/odata-datasources.mdx` gains an "OData v2 services" section with the `version: '2.0'` line, a v4/v2 spelling table, and what stays out. `README.md` and the package description name v2. - **`.changeset/28125-driver-odata-v2-dialect.md`**: `@objectstack/driver-odata` minor and `@objectstack/spec` minor (`added`). ## Why ### Before (measured at `origin/main` `062a2323`, production sources reverted, new tests kept) - driver-odata: 627 failed, 732 passed. Every v2 test failed: 336 in the case-sets, 276 in the kit and 15 in the recorded-driver suite, each on `driver-odata: invalid config — version: Invalid input: expected "4.0"`. `dialect-v2.test.ts` could not load, because the module did not exist. The 732 passing tests are the whole v4 suite. - service-datasource v2 import: 5 failed (the same config refusal), 13 passed (v4). - spec `type-compat.test.ts`: 1 failed, `expected 'unknown' to be 'datetime'`. ### After All green: driver-odata 1383 passed, the service-datasource import 18 passed, and spec `type-compat` 29 passed. ### Clause-② and the kind `yes (widening)`: the driver config accepts a second `version` value, and the type matrix answers `datetime` / `time` where it answered `unknown` (a draft typed those columns `text` for review, and Gate 2 refused a `datetime` / `time` field over one). Nothing accepted before is refused now. `Metadata-protocol: added`: `version: '2.0'` is a new value a datasource author can write, so both moved packages take `minor`. ### One finding met on the way The plugin-driver kit's "temporal: every row, as stored" check failed on the first v2 run. A `date` field over v2's `Edm.DateTime` read back as `1969-12-31T00:00:00.000Z` while the remote held `1969-12-31`. v2 has no `Edm.Date`, and a v2 service keeps a date in an `Edm.DateTime` at midnight. So the driver now reads the date alone under a field the object declares `date`, and the kit registers its object with its `fields`, as the engine does. ## Verification - **Pins red first, ablated, restored.** Each ablation used `scripts/ablation-replace.mjs` on the committed WIP head, each restored with `git diff HEAD` empty. Every one went red: - the envelope (`d.results`) - the Association `ReferentialConstraint` - the AssociationSet bindings - `substringof` - the numeric suffixes - the `datetime'…'` prefix - the `guid'…'` key literal - `$inlinecount` - `version` accepting `2.0` - `2.0` selecting the v2 dialect - the spec aliases - `MERGE` - a date field over `Edm.DateTime` - the dropped `__deferred` stubs One extra run stayed green: the spec-alias ablation against the service-datasource import test. service-datasource resolves `@objectstack/spec` from its built `dist`, so a `src` mutation does not reach it. The alias pin is the spec test, which went red. - **The v4 suite is unchanged.** Its 732 tests passed on `main`'s sources and pass on this head. The shared test files gained a version loop, and the v4 run keeps its exact describe names. - **Gates.** On the committed head `667660ad`, after a whole-tree `turbo run build` (exit 0): `dispatch-gates --commands` derived 117 families, all run, all exit 0. `--ran`: 117 derived, 117 run, 0 NOT-MEASURED. Also run: `pnpm lint` exit 0; `typecheck` of driver-odata, service-datasource and spec, each exit 0; driver-odata 1383 passed; service-datasource 1074 passed; spec `src/data` 4591 passed. The head is 15 commits behind `origin/main`; none of them touches a file this diff changes. - **Generated spec artifacts.** `pnpm --filter @objectstack/spec build` then `check:generated`: all 14 up to date (the alias table feeds no artifact). ## Acceptance notes - **The v2 fixtures are written from the services' published shape, not recorded live.** The container has no route to services.odata.org. The XML header says what is abridged and what is fixture-own, as the v4 TripPin fixture's header does. - **`Prefer: odata.maxpagesize` is still sent on a v2 GET.** A v2 service ignores a preference it does not know (RFC 7240 §2), and the page size it uses is its own. The docs and the `pageSize` describe say so. The transport is untouched. - **A zone-less date comparand on `Edm.DateTime` is not pushed down.** It is left to the engine, the same rule v4 applies to `Edm.DateTimeOffset`, so the service is never asked a narrower question. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (Two alias entries in a pure mapping table.) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: its generated artifacts are checked (`check:generated`: all up to date, nothing to regenerate). - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English (the 维护者速读 block is the repo's own Chinese reader section). - [x] The *Before Submitting* list in CONTRIBUTING.md is done. --- _Generated by [Claude Code](https://claude.ai/code/session_016WV4cykf9LVyVYxnw1NL95)_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016WV4cykf9LVyVYxnw1NL95 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7d846b5 commit d359857

22 files changed

Lines changed: 2291 additions & 309 deletions
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/driver-odata': minor
3+
'@objectstack/spec': minor
4+
---
5+
6+
feat(driver-odata): an OData v2 service is served by the same driver with `version: '2.0'` — the verbose JSON envelope, `$inlinecount`, typed literals, `MERGE`, and `$metadata` Associations read into the lookups an import drafts
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: added
10+
11+
`createODataDriverPlugin({ …, version: '2.0' })` (and `parseODataDriverConfig`) now accepts `'2.0'` beside `'4.0'`, which stays the default. Before, any value but `'4.0'` refused the boot. The v2 dialect (`odataV2Dialect`, `parseCsdlV2`, exported beside the v4 ones) plugs into the existing dialect seam; the driver and the `$filter` translator are unchanged, so the never-narrower rule, paging, keys, ETags and the error mapping hold as on v4. Only the public v2 protocol ships: SAP Gateway's CSRF token handshake, `sap-client` and the `sap:*` annotations stay out of this repository, for a vendor plugin.
12+
13+
- **Requests and the envelope.** Requests carry `DataServiceVersion: 2.0` and ask for `application/json`. A collection is read out of `d.results` (or a 1.0 service's bare `d` array), with `__next` followed as `@odata.nextLink` is and the string `__count` read as the count. An entity is read out of `d`; its ETag comes from `__metadata.etag`; `__metadata` and each navigation's `{ __deferred }` stub are dropped from the row.
14+
- **Count.** `count` sends `$inlinecount=allpages&$top=0`.
15+
- **`$filter`.** `$contains` is `substringof('x',P)`; `startswith` / `endswith` / `tolower` as on v4. Literals are typed: `datetime'…'`, `datetimeoffset'…'`, `time'PT…'`, `guid'…'`, `7L`, `1.5M` (no exponent), `1.5d`, `1.5f`. An `Edm.DateTime` comparand is pushed down only when it names its instant (a zone, an offset or a `Date`), as that instant's UTC wall clock; a bare date or a zone-less date-time is left to the engine.
16+
- **Keys.** Key predicates take the same typed literals (`Shipments(guid'…')`, `Items(7L)`), and a composite key's `id` (`TrackingId=guid'…',Leg=2L`) is read back into the same predicate.
17+
- **Values.** `Edm.DateTime` (`/Date(ms)/`) is read as the UTC instant, or as its date under a field the object declares `date` (v2 has no `Edm.Date`); `Edm.Int64` and `Edm.Decimal` text is read as a number (an `Edm.Int64` beyond the safe range stays text); `Edm.Time` (`PT13H20M`) is read as the time of day. Writes go out in the same forms.
18+
- **Writes.** `create` is `POST`; a partial update is `MERGE`, sent as a `POST` tunnelled with `X-HTTP-Method: MERGE`; `delete` is `DELETE`. `If-Match` carries the `__metadata.etag` of the row's last read.
19+
- **`$metadata`.** EDMX 1.0 is read: a navigation's target and cardinality come from its `Association` ends, its foreign key from the Association's `ReferentialConstraint` when this type is the dependent end, and its target entity set from the `AssociationSet`s. `introspectSchema()` reports dialect `odata-2.0` and the same navigation shape v4 yields, so `generateObjectDraft` / `importObject` draft the same lookups over either version. A v2 document under `version: '4.0'`, or a v4 one under `'2.0'`, is refused with the version that reads it named.
20+
- **The dialect seam** gains four optional members (`readValue`, `writeValue`, `keyValue`, `updateRequest`); a dialect that leaves one out keeps the v4 behaviour, so an out-of-repo dialect still compiles. `readValue` is given the declared type of the field a property backs, which the driver now records from the object's `fields` in `registerExternalObject`.
21+
- **`@objectstack/spec`:** the type matrix's `odata` arm reads the two primitives only v2's CSDL declares, `Edm.DateTime` → `datetime` and `Edm.Time` → `time`. Both were `unknown`: a draft typed them `text` and flagged them for review, and a `datetime` or `time` field declared over one failed Gate 2 as a `type_mismatch`. Nothing that passed before fails now.

‎content/docs/data-modeling/odata-datasources.mdx‎

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
---
22
title: "OData datasources — ERP records, live, with no driver of your own"
33
navTitle: OData Datasources
4-
description: Declare a datasource over an OData v4 service such as SAP S/4HANA or Dynamics 365, bind objects to its entity sets, and read and write them live, with no code per entity set.
4+
description: Declare a datasource over an OData v4 or v2 service such as SAP S/4HANA, SAP Gateway or Dynamics 365, bind objects to its entity sets, and read and write them live, with no code per entity set.
55
---
66

77
Many business systems already publish their records over **OData v4**: SAP S/4HANA,
88
Microsoft Dynamics 365 and Dataverse, and any service that serves a CSDL 4.0
9-
`$metadata` document. For those you need no driver of your own. Mount
9+
`$metadata` document. Many others still speak **OData v2**: most SAP Gateway services,
10+
the older Dynamics CRM `OrganizationData.svc` and SharePoint 2013 REST. For those you
11+
need no driver of your own. Mount
1012
`@objectstack/driver-odata` once per service, declare a datasource, and bind an object
1113
to each entity set you want. Every view, form, permission set, formula and hook then
1214
works on those records, and no row is stored on the platform side.
@@ -20,16 +22,17 @@ says when to choose it over a driver of your own or a synced copy.
2022

2123
Everything on this page is what the driver's own tests assert, against an in-process
2224
OData service and behind the real engine
23-
(`packages/drivers/driver-odata/src/odata-driver.floor.test.ts` and
24-
`odata-driver.conformance.test.ts`). Read them when this page and the runtime seem to
25-
disagree.
25+
(`packages/drivers/driver-odata/src/odata-driver.floor.test.ts`,
26+
`odata-driver.conformance.test.ts`, which runs over a v4 and a v2 service, and
27+
`odata-driver-v2.test.ts`). Read them when this page and the runtime seem to disagree.
2628

2729
## Check the service first
2830

29-
- **It speaks OData v4.** Open `<serviceRoot>$metadata`. The root element must say
30-
`Version="4.0"`. Only the v4 dialect ships. A service that speaks only OData v2 needs a
31-
v2 dialect, which this package does not include. Many older SAP Gateway services are
32-
v2 only, so check each service you plan to bind.
31+
- **Which OData version it speaks.** Open `<serviceRoot>$metadata`. A root element
32+
that says `Version="4.0"` is a v4 service, the default. One that says `Version="1.0"`
33+
(EDMX 1.0, with `m:DataServiceVersion="2.0"` or `"1.0"` below it) is a v2 service:
34+
declare `version: '2.0'` ([OData v2 services](#odata-v2-services)). A document of the
35+
other version is refused at the first request, with the version that reads it named.
3336
- **Each entity set you bind has a key.** The key becomes the record's `id`. An entity
3437
type with no usable key is refused at the first request.
3538
- **You know how it authenticates.** The driver sends no credential, a bearer token, or
@@ -61,15 +64,15 @@ export default defineStack({
6164
| :--- | :--- | :--- |
6265
| `name` | `odata` | The datasource it serves. It is also the driver name and the `driver.<name>` service suffix. |
6366
| `serviceRoot` | (required) | The service root URL, http or https, with no credentials and no query string. |
64-
| `version` | `4.0` | The protocol version. `4.0` is the only value. |
67+
| `version` | `4.0` | The protocol version: `4.0` or `2.0` ([OData v2 services](#odata-v2-services)). |
6568
| `auth` | `{ scheme: 'none' }` | `none`, `bearer`, or `basic` with a `username`. The secret is the datasource's `external.credentialsRef`. Leave it `none` when the datasource borrows a connector instance's credential ([Credentials](#3-credentials)). |
6669
| `secrets` | (none) | Where `credentialsRef` is resolved: an object with `resolve(credentialsRef)`. When absent, the host's `datasource-secret-resolver` service is used; `os serve` registers one. |
67-
| `pageSize` | `500` | Rows asked for per server page (`Prefer: odata.maxpagesize`). |
70+
| `pageSize` | `500` | Rows asked for per server page (`Prefer: odata.maxpagesize`). A v2 service has no such preference and pages by its own size. |
6871
| `requestTimeoutMs` | `30000` | The timeout of each request. |
6972

7073
The options are checked when the plugin is built. An unknown key, a `serviceRoot` with
71-
a user name or a query in it, or a version other than `4.0` refuses the boot with every
72-
problem named.
74+
a user name or a query in it, or a version other than `4.0` or `2.0` refuses the boot
75+
with every problem named.
7376

7477
## 2. Declare the datasource and bind objects
7578

@@ -324,6 +327,54 @@ and the ways out are the same as on any plugin driver
324327
- **Editable data:** declare `sharingModel: 'public_read_write'`, and decide who may edit
325328
in a `beforeUpdate` hook. A member's delete is still refused on this model.
326329

330+
## OData v2 services
331+
332+
Declare the version on the plugin; nothing else in the datasource or the objects changes:
333+
334+
```typescript
335+
createODataDriverPlugin({
336+
name: 'gateway',
337+
serviceRoot: 'https://erp.example.com/sap/opu/odata/sap/ZSALES_SRV/',
338+
version: '2.0',
339+
auth: { scheme: 'basic', username: 'SVC_PLATFORM' },
340+
});
341+
```
342+
343+
The driver then speaks the public v2 protocol. Everything above holds, spelled the v2 way:
344+
345+
| | v4 | v2 |
346+
| :--- | :--- | :--- |
347+
| Request headers | `OData-Version: 4.0` | `DataServiceVersion: 2.0`, `Accept: application/json` (verbose JSON) |
348+
| A collection | `value`, `@odata.nextLink`, `@odata.count` | `d.results`, `__next`, `__count` (a 1.0 service's bare `d` array is read too) |
349+
| The count | `$count=true` | `$inlinecount=allpages` |
350+
| `$contains` | `contains(P,'x')` | `substringof('x',P)` |
351+
| Literals | `2024-03-01T10:00:00Z`, a bare GUID, `1.5` | `datetime'…'`, `datetimeoffset'…'`, `time'PT13H20M0S'`, `guid'…'`, `7L`, `1.5M`, `1.5d`, `1.5f` |
352+
| Keys | `Orders(4711)` | the same typed literals: `Shipments(guid'…')`, `Items(7L)` |
353+
| A partial update | `PATCH` | `POST` with `X-HTTP-Method: MERGE` |
354+
| The ETag | `@odata.etag` | `__metadata.etag` |
355+
| `$metadata` | CSDL 4.0, navigation with its `ReferentialConstraint` | EDMX 1.0: `Association` / `AssociationSet`, the constraint on the Association |
356+
357+
- **Values are read the way the engine reads them.** `Edm.DateTime` arrives as
358+
`/Date(<ms>)/` and is read as that UTC instant (`1996-07-04T00:00:00.000Z`).
359+
`Edm.Int64` and `Edm.Decimal` arrive as text and are read as numbers, except an
360+
`Edm.Int64` beyond JavaScript's safe range, which stays text. `Edm.Time` is a duration
361+
since midnight (`PT13H20M`) and is read as the time of day (`13:20:00`). Writes go out in
362+
the same forms.
363+
- **v2 has no `Edm.Date`.** A v2 service keeps a date in an `Edm.DateTime` at midnight.
364+
Declare the field `date`, and the driver reads the date alone (`1996-07-04`).
365+
- **A zone-less date comparand is left to the engine.** A filter on an `Edm.DateTime`
366+
is pushed down only when its value names its instant (`…Z`, an offset, or a `Date`).
367+
A bare date or a date-time without a zone is applied by the engine instead, so the
368+
service is never asked a narrower question.
369+
- **The import reads Associations.** An imported `Edm.DateTime` becomes a `datetime`
370+
field and an `Edm.Time` a `time` field. A navigation whose Association carries a
371+
`ReferentialConstraint`, with this entity type on its dependent end, becomes the same
372+
`lookup` a v4 navigation does.
373+
- **What stays out of this package.** Only the public v2 protocol ships here. Vendor
374+
mechanics stay out: SAP Gateway's CSRF token handshake, the `sap-client` parameter and
375+
the `sap:*` annotations, for example. A service that needs one is served by a vendor
376+
plugin.
377+
327378
## When the service fails
328379

329380
| What happened | The caller is answered |
@@ -340,7 +391,8 @@ as a sharing scope. The full mapping is in
340391

341392
## What it does not do
342393

343-
- **OData v2.** Only the v4 dialect ships.
394+
- **Vendor-specific mechanics** on a v2 service, such as SAP's CSRF token handshake or
395+
`sap-client` ([OData v2 services](#odata-v2-services)).
344396
- **`$expand` and navigation properties.** Bind each related entity set as an object of
345397
its own; an imported foreign key becomes a `lookup` onto it.
346398
- **Actions and functions** of the service. `execute` is refused.

‎packages/drivers/driver-odata/README.md‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# @objectstack/driver-odata
22

3-
An OData v4 datasource driver for ObjectStack. Declare a datasource over an OData
4-
service (SAP S/4HANA, Dynamics 365, Dataverse, any CSDL 4.0 service), bind objects to
3+
An OData v4 and v2 datasource driver for ObjectStack. Declare a datasource over an OData
4+
service (SAP S/4HANA, Dynamics 365, Dataverse, any CSDL 4.0 service; with `version: '2.0'`,
5+
SAP Gateway and any other EDMX 1.0 service), bind objects to
56
its entity sets, and every view, form, permission and hook works on them, live. No row
67
is stored on the platform side, and no code is written per entity set.
78

@@ -66,10 +67,10 @@ export default defineStack({
6667
| :--- | :--- | :--- |
6768
| `name` | `odata` | The datasource name, the driver name and its `driver.<name>` service suffix. |
6869
| `serviceRoot` | — | The service root URL (http or https, no credentials, no query). |
69-
| `version` | `4.0` | The protocol version. Only the v4 dialect ships. |
70+
| `version` | `4.0` | The protocol version: `4.0`, or `2.0` for the v2 dialect (verbose JSON, `$inlinecount`, typed literals, `MERGE`, Associations). Vendor mechanics such as SAP's CSRF handshake are not in this package. |
7071
| `auth` | `{ scheme: 'none' }` | `none`, `bearer`, or `basic` with a `username`. The secret is the datasource's `external.credentialsRef`. Leave it `none` when the datasource borrows a connector instance's credential (below). |
7172
| `secrets` | (none) | An object with `resolve(credentialsRef)` that the secret is resolved through. When absent, the kernel service `datasource-secret-resolver` is read at the first request; `os serve` registers it over its own `sys_secret` store. A host composing its own kernel passes the binder it gives `DatasourceAdminServicePlugin`, or registers that service. |
72-
| `pageSize` | `500` | Rows asked for per server page (`Prefer: odata.maxpagesize`). |
73+
| `pageSize` | `500` | Rows asked for per server page (`Prefer: odata.maxpagesize`; a v2 service pages by its own size). |
7374
| `requestTimeoutMs` | `30000` | Per-request timeout. |
7475

7576
## Borrowing a connector instance's credential

‎packages/drivers/driver-odata/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@objectstack/driver-odata",
33
"version": "18.2.0",
44
"license": "Apache-2.0",
5-
"description": "OData v4 datasource driver for ObjectStack — serve an OData service's entity sets as external objects through a plugin driver",
5+
"description": "OData v4 and v2 datasource driver for ObjectStack — serve an OData service's entity sets as external objects through a plugin driver",
66
"keywords": [
77
"objectstack",
88
"driver",

0 commit comments

Comments
 (0)