Skip to content

Commit 88b8a2b

Browse files
committed
wip: changesets; regenerated spec api-surface and reference pages (#27192)
Claude-Session: https://claude.ai/code/session_01Utp6CYjLJ7sxfUehRrLbT3 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2d3501f commit 88b8a2b

7 files changed

Lines changed: 45 additions & 2 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
feat(spec): the error-code ledger registers `ERR_EXCHANGE_RATE_MISSING` and `ERR_HOME_CURRENCY_UNSET`, and `@objectstack/spec/data` exports `CURRENCY_FRACTION_DIGITS` (ADR-0144 D5, Open items 3 and 9)
6+
7+
Clause-②: yes (widening: two new error codes under `@objectstack/objectql` in `ERROR_CODE_LEDGER`; the `CURRENCY_FRACTION_DIGITS` table, already in the package, is now exported from `@objectstack/spec/data`. No Zod schema shape and no table value changes)
8+
Metadata-protocol: none
9+
10+
⛔ Not released alone: these ship in release R2 with the write-time home-currency projection that answers them (ADR-0144 D9).
11+
12+
- `ERR_EXCHANGE_RATE_MISSING` (400): a write that must convert an amount into the home currency found no exchange rate in force on the record's document date.
13+
- `ERR_HOME_CURRENCY_UNSET` (400): the same write found no home currency, because the organization's `localization.currency` setting is not set.
14+
- `CURRENCY_FRACTION_DIGITS` (ISO 4217 minor units per code) is the table the engine rounds a home amount with, exported so a consumer rounds with the same table instead of a copy.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/objectql': patch
3+
---
4+
5+
feat(objectql): a record-mode amount that declares `currencyConfig.homeAmount` stores its value converted into the home currency, with the rate used, on every write (ADR-0144 D5)
6+
7+
Clause-②: yes (widening: `ObjectQL.setHomeCurrencyResolver(resolver)` is new public engine surface; the write refusals below reach only an object that declares `currencyConfig.homeAmount`, a key that ships in this same release, so no released accept set narrows)
8+
Metadata-protocol: none
9+
10+
⛔ Not released alone: this is the engine half of release R2 (ADR-0144 D9). It ships in the same release as the `currencyConfig.homeAmount` key (`@objectstack/spec`), never ahead of it and never without it.
11+
12+
When a record is inserted, and when an update changes its amount, its currency code or its document date, the engine writes the two fields `homeAmount` names:
13+
14+
- **The rate** is the organization's `sys_exchange_rate` row for the record's currency with the latest `effective_date` on or before the document date (`homeAmount.rateDate`). A `datetime` document date is read as its UTC calendar day. The row's rate is stored in `homeAmount.rateField` as it is, unrounded.
15+
- **The home amount** is the amount divided by the rate (a rate states how many units of the foreign currency one unit of the home currency buys), computed in exact decimal arithmetic and rounded once, half away from zero, to the home currency's minor units (0 for JPY, 2 for USD and CNY, 3 for KWD). USD 10,000 at 0.1389 into CNY stores 71994.24.
16+
- **Same currency as home:** rate `1`, no rate read. **Blank amount:** blank home amount and rate.
17+
- **The home currency** is the `localization.currency` setting, read for the row's organization through the `settings` service. `ObjectQLPlugin` wires it; an engine booted without the plugin answers it through `setHomeCurrencyResolver`.
18+
- **The engine's alone.** A value a caller sends for either field is dropped (or refused under `strictReadonlyWrites`) as before; a `defaultValue`, a system write or a historical (`preserveAudit`) import of either field is computed over.
19+
- **Refusals, before anything is written:** `ERR_EXCHANGE_RATE_MISSING` / 400 when no rate row is in force on the document date (the message names the currency and the date; never a rate of 1, a later rate or an empty home amount); `ERR_HOME_CURRENCY_UNSET` / 400 when the organization has no `localization.currency` (never an assumed home currency); `VALIDATION_FAILED` `required` on the date field when a foreign-currency amount has no document date. A multi-row insert (`insertMany`, the import) refuses only the offending row; an update by filter is refused when its one patch would convert the matched rows to different home amounts.
20+
- **Every door, one path.** REST create and update, the import (batched and per row, dry run included) and `validate()` reach the same insert and update seam, so they convert and refuse alike.
21+
- **No migration.** Nothing changes for an amount that does not declare `homeAmount`.

‎content/docs/references/api/automation-api.mdx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ const result = AutomationApiErrorCode.parse(data);
347347

348348
| Property | Type | Required | Description |
349349
| :--- | :--- | :--- | :--- |
350-
| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +353 more>` | ✅ | The inner refusal's code in its WIRE spelling - the ADR-0112 member a record save of the same write answers in `error.code` (e.g. `UNIQUE_VIOLATION`); the closed set `ApiErrorSchema.code` parses against |
350+
| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +355 more>` | ✅ | The inner refusal's code in its WIRE spelling - the ADR-0112 member a record save of the same write answers in `error.code` (e.g. `UNIQUE_VIOLATION`); the closed set `ApiErrorSchema.code` parses against |
351351
| **message** | `string` | ✅ | The inner refusal's own diagnostic text, verbatim, without the node and operation prefixes the run's failure text adds |
352352

353353
### Allowed Values: `FlowInnerRefusal.code`
@@ -483,8 +483,10 @@ const result = AutomationApiErrorCode.parse(data);
483483
* `ERR_CURRENCY_CODE_REQUIRED`
484484
* `ERR_DATASOURCE_UNAVAILABLE`
485485
* `ERR_DRIVER_CONNECT`
486+
* `ERR_EXCHANGE_RATE_MISSING`
486487
* `ERR_FILE_CONSTRAINT`
487488
* `ERR_FILE_REFERENCE_COPY`
489+
* `ERR_HOME_CURRENCY_UNSET`
488490
* `ERR_HOOK_TARGET_REBIND`
489491
* `ERR_READONLY_FIELD_REJECTED`
490492
* `ERR_SUMMARY_RECOMPUTE`

‎content/docs/references/api/contract.mdx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const result = ApiErrorSchema.parse(data);
2828

2929
| Property | Type | Required | Description |
3030
| :--- | :--- | :--- | :--- |
31-
| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +353 more>` | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) |
31+
| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +355 more>` | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) |
3232
| **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) |
3333
| **message** | `string` | ✅ | Readable error message |
3434
| **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. |
@@ -171,8 +171,10 @@ const result = ApiErrorSchema.parse(data);
171171
* `ERR_CURRENCY_CODE_REQUIRED`
172172
* `ERR_DATASOURCE_UNAVAILABLE`
173173
* `ERR_DRIVER_CONNECT`
174+
* `ERR_EXCHANGE_RATE_MISSING`
174175
* `ERR_FILE_CONSTRAINT`
175176
* `ERR_FILE_REFERENCE_COPY`
177+
* `ERR_HOME_CURRENCY_UNSET`
176178
* `ERR_HOOK_TARGET_REBIND`
177179
* `ERR_READONLY_FIELD_REJECTED`
178180
* `ERR_SUMMARY_RECOMPUTE`

‎content/docs/references/api/error-code-ledger.mdx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,8 +338,10 @@ const result = ErrorCode.parse(data);
338338
* `ERR_CURRENCY_CODE_REQUIRED`
339339
* `ERR_DATASOURCE_UNAVAILABLE`
340340
* `ERR_DRIVER_CONNECT`
341+
* `ERR_EXCHANGE_RATE_MISSING`
341342
* `ERR_FILE_CONSTRAINT`
342343
* `ERR_FILE_REFERENCE_COPY`
344+
* `ERR_HOME_CURRENCY_UNSET`
343345
* `ERR_HOOK_TARGET_REBIND`
344346
* `ERR_READONLY_FIELD_REJECTED`
345347
* `ERR_SUMMARY_RECOMPUTE`

‎packages/spec/api-surface/data.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
"CROSS_FIELD_COMPARISON_CLASSES (const)",
9999
"CROSS_FIELD_COMPARISON_TYPE_CLASSES (const)",
100100
"CROSS_FIELD_NO_CLASS_REASONS (const)",
101+
"CURRENCY_FRACTION_DIGITS (const)",
101102
"CalendarDateValue (type)",
102103
"CalendarDateValueSchema (const)",
103104
"ClockTimeValue (type)",

‎packages/spec/export-origins/data.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
"CROSS_FIELD_COMPARISON_CLASSES": "src/data/filter-cross-field-comparison-class.ts#CROSS_FIELD_COMPARISON_CLASSES (const)",
9696
"CROSS_FIELD_COMPARISON_TYPE_CLASSES": "src/data/filter-cross-field-comparison-class.ts#CROSS_FIELD_COMPARISON_TYPE_CLASSES (const)",
9797
"CROSS_FIELD_NO_CLASS_REASONS": "src/data/filter-cross-field-comparison-class.ts#CROSS_FIELD_NO_CLASS_REASONS (const)",
98+
"CURRENCY_FRACTION_DIGITS": "src/data/currency-fraction-digits.ts#CURRENCY_FRACTION_DIGITS (const)",
9899
"CalendarDateValue": "src/data/field-value.zod.ts#CalendarDateValue (type)",
99100
"CalendarDateValueSchema": "src/data/field-value.zod.ts#CalendarDateValueSchema (const)",
100101
"ClockTimeValue": "src/data/field-value.zod.ts#ClockTimeValue (type)",

0 commit comments

Comments
 (0)