diff --git a/ContractTests/Client/package.json b/ContractTests/Client/package.json
index a593405a..78cb6287 100644
--- a/ContractTests/Client/package.json
+++ b/ContractTests/Client/package.json
@@ -1,6 +1,6 @@
{
"name": "@cratis/arc.core-client-contract",
- "version": "0.57.0",
+ "version": "0.57.1",
"private": true,
"type": "module",
"dependencies": {
diff --git a/ContractTests/ProxyComparison/Snapshots/TypeScript/ProxyComparison/All.ts b/ContractTests/ProxyComparison/Snapshots/TypeScript/ProxyComparison/All.ts
index 0b5dad49..757c6ff4 100644
--- a/ContractTests/ProxyComparison/Snapshots/TypeScript/ProxyComparison/All.ts
+++ b/ContractTests/ProxyComparison/Snapshots/TypeScript/ProxyComparison/All.ts
@@ -1,4 +1,4 @@
-// @generated by Cratis. Source: ProxyComparison.Listing.All. Time: 2026-10-02T15:23:26.2840000Z. Hash: DDEC51C0B1FCDD19940A0B638DF8AC2402F8EA3BC633F8A17F60F25321244CEF
+// @generated by Cratis. Source: ProxyComparison.Listing.All. Time: 2026-10-02T18:11:17.1450000Z. Hash: 60B4AB9724378EB1EBFF52DBF33EFA71B421A6633AB7316132F18F9B79CACFB1
/*---------------------------------------------------------------------------------------------
* **DO NOT EDIT** - This file is an automatically generated file.
*--------------------------------------------------------------------------------------------*/
@@ -13,7 +13,9 @@ import { Listing } from './Listing';
class AllSortBy {
readonly name: SortingActionsForQuery
;
+ /** @deprecated In-memory sorting on this field is rejected; database providers sort it by their own order. This helper will be removed in the next major release (https://github.com/Cratis/Arc.TypeScript/issues/177). Sort on a scalar field instead. */
readonly detail: SortingActionsForQuery;
+ /** @deprecated In-memory sorting on this field is rejected; database providers sort it by their own order. This helper will be removed in the next major release (https://github.com/Cratis/Arc.TypeScript/issues/177). Sort on a scalar field instead. */
readonly notice: SortingActionsForQuery;
readonly status: SortingActionsForQuery;
constructor(readonly query: All) {
@@ -25,7 +27,9 @@ class AllSortBy {
}
class AllSortByWithoutQuery {
readonly name = new SortingActions('name');
+ /** @deprecated In-memory sorting on this field is rejected; database providers sort it by their own order. This helper will be removed in the next major release (https://github.com/Cratis/Arc.TypeScript/issues/177). Sort on a scalar field instead. */
readonly detail = new SortingActions('detail');
+ /** @deprecated In-memory sorting on this field is rejected; database providers sort it by their own order. This helper will be removed in the next major release (https://github.com/Cratis/Arc.TypeScript/issues/177). Sort on a scalar field instead. */
readonly notice = new SortingActions('notice');
readonly status = new SortingActions('status');
}
diff --git a/ContractTests/ProxyComparison/Snapshots/TypeScript/ProxyComparison/Observe.ts b/ContractTests/ProxyComparison/Snapshots/TypeScript/ProxyComparison/Observe.ts
index f3b15442..de774a09 100644
--- a/ContractTests/ProxyComparison/Snapshots/TypeScript/ProxyComparison/Observe.ts
+++ b/ContractTests/ProxyComparison/Snapshots/TypeScript/ProxyComparison/Observe.ts
@@ -1,4 +1,4 @@
-// @generated by Cratis. Source: ProxyComparison.Listing.Observe. Time: 2026-10-02T15:23:26.2860000Z. Hash: 4B563871A5AF1853D3C85AB5D3A3AF5882D395039B02DCAC180B733C4E641B96
+// @generated by Cratis. Source: ProxyComparison.Listing.Observe. Time: 2026-10-02T18:11:17.1480000Z. Hash: 24D10616E40D92A9E4CF9E4758F280A66CD87BA96AE162492F39CDFF4C267099
/*---------------------------------------------------------------------------------------------
* **DO NOT EDIT** - This file is an automatically generated file.
*--------------------------------------------------------------------------------------------*/
@@ -13,7 +13,9 @@ import { Listing } from './Listing';
class ObserveSortBy {
readonly name: SortingActionsForObservableQuery;
+ /** @deprecated In-memory sorting on this field is rejected; database providers sort it by their own order. This helper will be removed in the next major release (https://github.com/Cratis/Arc.TypeScript/issues/177). Sort on a scalar field instead. */
readonly detail: SortingActionsForObservableQuery;
+ /** @deprecated In-memory sorting on this field is rejected; database providers sort it by their own order. This helper will be removed in the next major release (https://github.com/Cratis/Arc.TypeScript/issues/177). Sort on a scalar field instead. */
readonly notice: SortingActionsForObservableQuery;
readonly status: SortingActionsForObservableQuery;
constructor(readonly query: Observe) {
@@ -25,7 +27,9 @@ class ObserveSortBy {
}
class ObserveSortByWithoutQuery {
readonly name = new SortingActions('name');
+ /** @deprecated In-memory sorting on this field is rejected; database providers sort it by their own order. This helper will be removed in the next major release (https://github.com/Cratis/Arc.TypeScript/issues/177). Sort on a scalar field instead. */
readonly detail = new SortingActions('detail');
+ /** @deprecated In-memory sorting on this field is rejected; database providers sort it by their own order. This helper will be removed in the next major release (https://github.com/Cratis/Arc.TypeScript/issues/177). Sort on a scalar field instead. */
readonly notice = new SortingActions('notice');
readonly status = new SortingActions('status');
}
diff --git a/ContractTests/ProxyComparison/differences.json b/ContractTests/ProxyComparison/differences.json
index 38182958..8df6b130 100644
--- a/ContractTests/ProxyComparison/differences.json
+++ b/ContractTests/ProxyComparison/differences.json
@@ -24,7 +24,7 @@
"complex-sorting": {
"category": "knownLimitation",
"issue": "https://github.com/Cratis/Arc.TypeScript/issues/174",
- "description": "Both generators emit helpers for complex result fields when generating result-oriented helpers (TypeScript here; .NET controller-based queries). Fields such as detail and notice are not sortable scalars: these fixture objects sort as no-ops on the TypeScript server and throw on .NET IQueryable. The model-bound .NET snapshot's query-parameter defect masks this separate limitation."
+ "description": "The .NET controller-based generator still emits helpers for complex result fields, which throw on .NET IQueryable; its model-bound snapshot's query-parameter defect masks that limitation. TypeScript retains detail/notice helpers for compatibility but deprecates them for removal in the next major release (https://github.com/Cratis/Arc.TypeScript/issues/177), and rejects in-memory sorting on these complex fields with the existing malformedRequest validation result. Database providers retain their own ordering; scalar helpers are unchanged."
},
"enum-names": {
"category": "intentional",
@@ -38,8 +38,8 @@
"limitations": ["complex-sorting"],
"files": {
"ProxyComparison/All.ts": {
- "pairSha256": "7f6c29c8163e0a4f1f4cb891d3cbcc27cab70dd8b7a244c9c6851d1282854687",
- "reasons": ["layout", "imports", "query-source", "sorting"]
+ "pairSha256": "d8e61bfa0dd8347d4b70abe98d60161de95cf1307046d02626e8bf2bd5c10592",
+ "reasons": ["layout", "imports", "query-source", "sorting", "complex-sorting"]
},
"ProxyComparison/Detail.ts": {
"pairSha256": "80d080af34fba46f349ebfc4821d5d6f3a82eeb57f48787bf7a1fc228ee29dfd",
@@ -50,8 +50,8 @@
"reasons": ["layout"]
},
"ProxyComparison/Observe.ts": {
- "pairSha256": "a2a3cf002cb13ab725443374c48b0b66d93bf380345af43869c136b54ab5c65e",
- "reasons": ["layout", "imports", "query-source", "sorting"]
+ "pairSha256": "3cf3ce2d59571f12b9164791985f40f0a7bacbddf4f503955cd7f4547ccbdd76",
+ "reasons": ["layout", "imports", "query-source", "sorting", "complex-sorting"]
},
"ProxyComparison/Register.ts": {
"pairSha256": "a624987ea1833732cbc7169c96c1ca68f7159bd74bb44e3014a1f010e11d07dc",
diff --git a/ContractTests/ProxyComparison/raw.diff b/ContractTests/ProxyComparison/raw.diff
index c0b18b52..58c2aabd 100644
--- a/ContractTests/ProxyComparison/raw.diff
+++ b/ContractTests/ProxyComparison/raw.diff
@@ -1,52 +1,56 @@
--- DotNET/ProxyComparison/All.ts
+++ TypeScript/ProxyComparison/All.ts
@@ -1 +1 @@
--// @generated by Cratis. Source: ProxyComparison.Listing. Time: 2026-10-02T15:23:25.9639250Z. Hash: 0E155A2217FA241EB7A3F167FC3C62571830874898B0B657D2366B8EA0B3AE01
-+// @generated by Cratis. Source: ProxyComparison.Listing.All. Time: 2026-10-02T15:23:26.2840000Z. Hash: DDEC51C0B1FCDD19940A0B638DF8AC2402F8EA3BC633F8A17F60F25321244CEF
+-// @generated by Cratis. Source: ProxyComparison.Listing. Time: 2026-10-02T18:11:16.8531660Z. Hash: 0E155A2217FA241EB7A3F167FC3C62571830874898B0B657D2366B8EA0B3AE01
++// @generated by Cratis. Source: ProxyComparison.Listing.All. Time: 2026-10-02T18:11:17.1450000Z. Hash: 60B4AB9724378EB1EBFF52DBF33EFA71B421A6633AB7316132F18F9B79CACFB1
@@ -8,2 +8,2 @@
-import { QueryFor, QueryResultWithState, Sorting, SortingActions, SortingActionsForQuery, Paging } from '@cratis/arc/queries';
-import { useQuery, useQueryWithPaging, useSuspenseQuery, useSuspenseQueryWithPaging, PerformQuery, SetSorting, SetPage, SetPageSize, QueryWhen } from '@cratis/arc.react/queries';
+import { QueryFor, type QueryResultWithState, type Sorting, Paging, SortingActions, SortingActionsForQuery } from '@cratis/arc/queries';
+import { useQuery, useSuspenseQuery, useQueryWithPaging, useSuspenseQueryWithPaging, type SetPage, type SetPageSize, type PerformQuery, type SetSorting, QueryWhen } from '@cratis/arc.react/queries';
-@@ -15,2 +15,4 @@
+@@ -15,2 +15,6 @@
- private _id: SortingActionsForQuery;
-
+ readonly name: SortingActionsForQuery;
++ /** @deprecated In-memory sorting on this field is rejected; database providers sort it by their own order. This helper will be removed in the next major release (https://github.com/Cratis/Arc.TypeScript/issues/177). Sort on a scalar field instead. */
+ readonly detail: SortingActionsForQuery;
++ /** @deprecated In-memory sorting on this field is rejected; database providers sort it by their own order. This helper will be removed in the next major release (https://github.com/Cratis/Arc.TypeScript/issues/177). Sort on a scalar field instead. */
+ readonly notice: SortingActionsForQuery;
+ readonly status: SortingActionsForQuery;
-@@ -18 +20,4 @@
+@@ -18 +22,4 @@
- this._id = new SortingActionsForQuery('id', query);
+ this.name = new SortingActionsForQuery('name', query);
+ this.detail = new SortingActionsForQuery('detail', query);
+ this.notice = new SortingActionsForQuery('notice', query);
+ this.status = new SortingActionsForQuery('status', query);
-@@ -20,4 +24,0 @@
+@@ -20,4 +26,0 @@
-
- get id(): SortingActionsForQuery {
- return this._id;
- }
-@@ -25 +25,0 @@
+@@ -25 +27,0 @@
-
-@@ -27,5 +27,4 @@
+@@ -27,5 +29,6 @@
- private _id: SortingActions = new SortingActions('id');
-
- get id(): SortingActions {
- return this._id;
- }
+ readonly name = new SortingActions('name');
++ /** @deprecated In-memory sorting on this field is rejected; database providers sort it by their own order. This helper will be removed in the next major release (https://github.com/Cratis/Arc.TypeScript/issues/177). Sort on a scalar field instead. */
+ readonly detail = new SortingActions('detail');
++ /** @deprecated In-memory sorting on this field is rejected; database providers sort it by their own order. This helper will be removed in the next major release (https://github.com/Cratis/Arc.TypeScript/issues/177). Sort on a scalar field instead. */
+ readonly notice = new SortingActions('notice');
+ readonly status = new SortingActions('status');
-@@ -38 +36,0 @@
+@@ -38 +40,0 @@
-
-@@ -65,3 +63,2 @@
+@@ -65,3 +67,2 @@
- get sortBy(): AllSortBy {
- return this._sortBy;
- }
+ get sortBy(): AllSortBy { return this._sortBy; }
+ static get sortBy(): AllSortByWithoutQuery { return this._sortBy; }
-@@ -69,4 +65,0 @@
+@@ -69,4 +69,0 @@
- static get sortBy(): AllSortByWithoutQuery {
- return this._sortBy;
- }
@@ -54,8 +58,8 @@
--- DotNET/ProxyComparison/Detail.ts
+++ TypeScript/ProxyComparison/Detail.ts
@@ -1 +1 @@
--// @generated by Cratis. Source: ProxyComparison.Detail. Time: 2026-10-02T15:23:25.9691550Z. Hash: 870244FDEBCE32A475902C261DDFF3AEADEFBA1A0B20D5FA623D6D1BD7473F94
-+// @generated by Cratis. Source: ProxyComparison.Detail. Time: 2026-10-02T15:23:26.2850000Z. Hash: EB083CE16EB7845B06C37F70EA02EC4D9BDB54179F8A3E64467299B7702BA917
+-// @generated by Cratis. Source: ProxyComparison.Detail. Time: 2026-10-02T18:11:16.8573780Z. Hash: 870244FDEBCE32A475902C261DDFF3AEADEFBA1A0B20D5FA623D6D1BD7473F94
++// @generated by Cratis. Source: ProxyComparison.Detail. Time: 2026-10-02T18:11:17.1470000Z. Hash: EB083CE16EB7845B06C37F70EA02EC4D9BDB54179F8A3E64467299B7702BA917
@@ -8,2 +8 @@
-import { field } from '@cratis/fundamentals';
-import { Guid } from '@cratis/fundamentals';
@@ -65,8 +69,8 @@
--- DotNET/ProxyComparison/Listing.ts
+++ TypeScript/ProxyComparison/Listing.ts
@@ -1 +1 @@
--// @generated by Cratis. Source: ProxyComparison.Listing. Time: 2026-10-02T15:23:25.9691550Z. Hash: 5E26E7FA2DA1134B949B271ED9019A0867B1FE2121E2BE76E9D4904F1F537957
-+// @generated by Cratis. Source: ProxyComparison.Listing. Time: 2026-10-02T15:23:26.2850000Z. Hash: EAAC3EB0B31A551AE2BC36EFD4D8F8EC34FCB11785A63CB982B2BEA7F1ACFBF5
+-// @generated by Cratis. Source: ProxyComparison.Listing. Time: 2026-10-02T18:11:16.8573780Z. Hash: 5E26E7FA2DA1134B949B271ED9019A0867B1FE2121E2BE76E9D4904F1F537957
++// @generated by Cratis. Source: ProxyComparison.Listing. Time: 2026-10-02T18:11:17.1470000Z. Hash: EAAC3EB0B31A551AE2BC36EFD4D8F8EC34FCB11785A63CB982B2BEA7F1ACFBF5
@@ -15,0 +16 @@
+
@@ -17,0 +19 @@
@@ -76,59 +80,63 @@
--- DotNET/ProxyComparison/Notice.ts
+++ TypeScript/ProxyComparison/Notice.ts
@@ -1 +1 @@
--// @generated by Cratis. Source: ProxyComparison.Notice. Time: 2026-10-02T15:23:25.9691550Z. Hash: C7BFC73EC13766A0F0B09DA0E21C97DFBF0018E89CB55A81E1C560094F91BA4E
-+// @generated by Cratis. Source: ProxyComparison.Notice. Time: 2026-10-02T15:23:26.2860000Z. Hash: C7BFC73EC13766A0F0B09DA0E21C97DFBF0018E89CB55A81E1C560094F91BA4E
+-// @generated by Cratis. Source: ProxyComparison.Notice. Time: 2026-10-02T18:11:16.8573780Z. Hash: C7BFC73EC13766A0F0B09DA0E21C97DFBF0018E89CB55A81E1C560094F91BA4E
++// @generated by Cratis. Source: ProxyComparison.Notice. Time: 2026-10-02T18:11:17.1480000Z. Hash: C7BFC73EC13766A0F0B09DA0E21C97DFBF0018E89CB55A81E1C560094F91BA4E
--- DotNET/ProxyComparison/Observe.ts
+++ TypeScript/ProxyComparison/Observe.ts
@@ -1 +1 @@
--// @generated by Cratis. Source: ProxyComparison.Listing. Time: 2026-10-02T15:23:25.9659790Z. Hash: 37AEFC99489891A80968EA738258F7C8759C9732BDCB0FD063B4C85F15651914
-+// @generated by Cratis. Source: ProxyComparison.Listing.Observe. Time: 2026-10-02T15:23:26.2860000Z. Hash: 4B563871A5AF1853D3C85AB5D3A3AF5882D395039B02DCAC180B733C4E641B96
+-// @generated by Cratis. Source: ProxyComparison.Listing. Time: 2026-10-02T18:11:16.8547150Z. Hash: 37AEFC99489891A80968EA738258F7C8759C9732BDCB0FD063B4C85F15651914
++// @generated by Cratis. Source: ProxyComparison.Listing.Observe. Time: 2026-10-02T18:11:17.1480000Z. Hash: 24D10616E40D92A9E4CF9E4758F280A66CD87BA96AE162492F39CDFF4C267099
@@ -8,2 +8,2 @@
-import { ObservableQueryFor, QueryResultWithState, Sorting, SortingActions, SortingActionsForObservableQuery, Paging, ChangeSet } from '@cratis/arc/queries';
-import { useObservableQuery, useObservableQueryWithPaging, useSuspenseObservableQuery, useSuspenseObservableQueryWithPaging, useChangeStream, SetSorting, SetPage, SetPageSize, ObservableQueryWhen } from '@cratis/arc.react/queries';
+import { ObservableQueryFor, type QueryResultWithState, type Sorting, Paging, SortingActions, SortingActionsForObservableQuery, type ChangeSet } from '@cratis/arc/queries';
+import { useObservableQuery, useSuspenseObservableQuery, useObservableQueryWithPaging, useSuspenseObservableQueryWithPaging, type SetPage, type SetPageSize, useChangeStream, type SetSorting, ObservableQueryWhen } from '@cratis/arc.react/queries';
-@@ -15,2 +15,4 @@
+@@ -15,2 +15,6 @@
- private _id: SortingActionsForObservableQuery;
-
+ readonly name: SortingActionsForObservableQuery;
++ /** @deprecated In-memory sorting on this field is rejected; database providers sort it by their own order. This helper will be removed in the next major release (https://github.com/Cratis/Arc.TypeScript/issues/177). Sort on a scalar field instead. */
+ readonly detail: SortingActionsForObservableQuery;
++ /** @deprecated In-memory sorting on this field is rejected; database providers sort it by their own order. This helper will be removed in the next major release (https://github.com/Cratis/Arc.TypeScript/issues/177). Sort on a scalar field instead. */
+ readonly notice: SortingActionsForObservableQuery;
+ readonly status: SortingActionsForObservableQuery;
-@@ -18 +20,4 @@
+@@ -18 +22,4 @@
- this._id = new SortingActionsForObservableQuery('id', query);
+ this.name = new SortingActionsForObservableQuery('name', query);
+ this.detail = new SortingActionsForObservableQuery('detail', query);
+ this.notice = new SortingActionsForObservableQuery('notice', query);
+ this.status = new SortingActionsForObservableQuery('status', query);
-@@ -20,4 +24,0 @@
+@@ -20,4 +26,0 @@
-
- get id(): SortingActionsForObservableQuery {
- return this._id;
- }
-@@ -25 +25,0 @@
+@@ -25 +27,0 @@
-
-@@ -27,5 +27,4 @@
+@@ -27,5 +29,6 @@
- private _id: SortingActions = new SortingActions('id');
-
- get id(): SortingActions {
- return this._id;
- }
+ readonly name = new SortingActions('name');
++ /** @deprecated In-memory sorting on this field is rejected; database providers sort it by their own order. This helper will be removed in the next major release (https://github.com/Cratis/Arc.TypeScript/issues/177). Sort on a scalar field instead. */
+ readonly detail = new SortingActions('detail');
++ /** @deprecated In-memory sorting on this field is rejected; database providers sort it by their own order. This helper will be removed in the next major release (https://github.com/Cratis/Arc.TypeScript/issues/177). Sort on a scalar field instead. */
+ readonly notice = new SortingActions('notice');
+ readonly status = new SortingActions('status');
-@@ -35 +33,0 @@
+@@ -35 +37,0 @@
-
-@@ -39 +36,0 @@
+@@ -39 +40,0 @@
-
-@@ -66,3 +63,2 @@
+@@ -66,3 +67,2 @@
- get sortBy(): ObserveSortBy {
- return this._sortBy;
- }
+ get sortBy(): ObserveSortBy { return this._sortBy; }
+ static get sortBy(): ObserveSortByWithoutQuery { return this._sortBy; }
-@@ -70,4 +65,0 @@
+@@ -70,4 +69,0 @@
- static get sortBy(): ObserveSortByWithoutQuery {
- return this._sortBy;
- }
@@ -136,8 +144,8 @@
--- DotNET/ProxyComparison/Register.ts
+++ TypeScript/ProxyComparison/Register.ts
@@ -1 +1 @@
--// @generated by Cratis. Source: ProxyComparison.Register. Time: 2026-10-02T15:23:25.9473890Z. Hash: 1D6FE14590B481D913125C1B2BE21D724F94D8AABAB561D47058830A6CADC8E2
-+// @generated by Cratis. Source: ProxyComparison.Register. Time: 2026-10-02T15:23:26.2870000Z. Hash: 9213A3CE667705E870E1AA87B3482F81F37362E3416D33C9BE11229F6762AC41
+-// @generated by Cratis. Source: ProxyComparison.Register. Time: 2026-10-02T18:11:16.8399350Z. Hash: 1D6FE14590B481D913125C1B2BE21D724F94D8AABAB561D47058830A6CADC8E2
++// @generated by Cratis. Source: ProxyComparison.Register. Time: 2026-10-02T18:11:17.1490000Z. Hash: 9213A3CE667705E870E1AA87B3482F81F37362E3416D33C9BE11229F6762AC41
@@ -10 +10 @@
-import { useCommand, SetCommandValues, ClearCommandValues } from '@cratis/arc.react/commands';
+import { useCommand, type SetCommandValues, type ClearCommandValues } from '@cratis/arc.react/commands';
@@ -157,8 +165,8 @@
--- DotNET/ProxyComparison/Status.ts
+++ TypeScript/ProxyComparison/Status.ts
@@ -1 +1 @@
--// @generated by Cratis. Source: ProxyComparison.Status. Time: 2026-10-02T15:23:25.9713430Z. Hash: 2021C7DF28DABBEC728CC5649211601AC22C9E7848AABC7C394B1DBBE4D65D34
-+// @generated by Cratis. Source: ProxyComparison.Status. Time: 2026-10-02T15:23:26.2870000Z. Hash: D6BE2F44AEE3F50AB357446E1374EEC78C1ABC0A6A249AADA64C9DC7327939E2
+-// @generated by Cratis. Source: ProxyComparison.Status. Time: 2026-10-02T18:11:16.8592410Z. Hash: 2021C7DF28DABBEC728CC5649211601AC22C9E7848AABC7C394B1DBBE4D65D34
++// @generated by Cratis. Source: ProxyComparison.Status. Time: 2026-10-02T18:11:17.1490000Z. Hash: D6BE2F44AEE3F50AB357446E1374EEC78C1ABC0A6A249AADA64C9DC7327939E2
@@ -8,2 +8,2 @@
- draft = 0,
- published = 1,
@@ -167,12 +175,12 @@
--- DotNET/ProxyComparison/UrgentNotice.ts
+++ TypeScript/ProxyComparison/UrgentNotice.ts
@@ -1 +1 @@
--// @generated by Cratis. Source: ProxyComparison.UrgentNotice. Time: 2026-10-02T15:23:25.9691550Z. Hash: B6DFD0F8172730462EE16C33D07A74E01C2C446E0DC8E33AF2A92DF22C4B1738
-+// @generated by Cratis. Source: ProxyComparison.UrgentNotice. Time: 2026-10-02T15:23:26.2880000Z. Hash: E68232937A2D329A758BE7A911099151B8188944D1CDE443CF39C4FF54CB4C7F
+-// @generated by Cratis. Source: ProxyComparison.UrgentNotice. Time: 2026-10-02T18:11:16.8573780Z. Hash: B6DFD0F8172730462EE16C33D07A74E01C2C446E0DC8E33AF2A92DF22C4B1738
++// @generated by Cratis. Source: ProxyComparison.UrgentNotice. Time: 2026-10-02T18:11:17.1490000Z. Hash: E68232937A2D329A758BE7A911099151B8188944D1CDE443CF39C4FF54CB4C7F
@@ -8 +8 @@
-import { field, derivedType } from '@cratis/fundamentals';
+import { derivedType, field } from '@cratis/fundamentals';
--- DotNET/ProxyComparison/index.ts
+++ TypeScript/ProxyComparison/index.ts
@@ -1,0 +1 @@
-+// @generated by Cratis. Source: . Time: 2026-10-02T15:23:26.2880000Z. Hash: 47EA7CF2735A3A1C1FD910D8F0AD46FA94CC8FA45699573458B54238BD829218
++// @generated by Cratis. Source: . Time: 2026-10-02T18:11:17.1500000Z. Hash: 47EA7CF2735A3A1C1FD910D8F0AD46FA94CC8FA45699573458B54238BD829218
diff --git a/ContractTests/ProxyComparison/runtime.mjs b/ContractTests/ProxyComparison/runtime.mjs
index b9fdf334..1c1df21b 100644
--- a/ContractTests/ProxyComparison/runtime.mjs
+++ b/ContractTests/ProxyComparison/runtime.mjs
@@ -35,7 +35,6 @@ const rows = [
{ name: 'alpha', detail: { id, created: '2026-01-01T03:04:05Z' }, notice: { title: 'first' }, status: 1 },
{ name: 'bravo', detail: { id, created: '2026-01-02T03:04:05Z' }, notice: { title: 'second' }, status: 0 }
];
-const originalOrder = rows.map(row => row.name);
const typescript = new ArcServer({ introspection: { enabled: false }, queries: [[All, 'All'], [Observe, 'Observe']].map(([Query, name]) => defineQuery({
name, path: new Query().route, schema: z.object({ id: z.string() }), perform: () => structuredClone(rows)
})) });
@@ -110,27 +109,27 @@ try {
assert.equal(status, 200, JSON.stringify(control));
assert.equal(control.isSuccess, true);
assert.deepEqual(control.data.map(row => row.name), ['alpha', 'bravo', 'charlie']);
- for (const name of sortNames) {
+ for (const name of new Set([...sortNames, 'detail', 'notice'])) {
for (const direction of ['ascending', 'descending']) {
- const sorting = Query.sortBy[name][direction];
+ const sorting = (Query.sortBy[name] ?? new SortingActions(name))[direction];
assert.equal(sorting.field, name);
- assert.deepEqual(query.sortBy[name][direction](), sorting, 'Instance and static helpers agree');
+ if (query.sortBy[name]) assert.deepEqual(query.sortBy[name][direction](), sorting, 'Instance and static helpers agree');
query.sorting = sorting;
const sorted = await query.perform({ id: Guid.parse(id) });
assert.equal(sentField, name, 'The browser client must send the field unchanged');
- if (name === 'id' || backend === 'DotNET' && ['detail', 'notice'].includes(name)) {
+ if (name === 'id' || ['detail', 'notice'].includes(name)) {
// Arc#2998: id is not a result field. Arc.TypeScript#174: complex fields are not comparable.
assert.equal(status, backend === 'DotNET' ? 500 : 400, JSON.stringify(sorted));
assert.equal(sorted.isSuccess, false);
assert.equal(sorted.hasExceptions, backend === 'DotNET');
+ if (backend === 'TypeScript') assert.deepEqual(sorted.validationResults.map(({ message, members, reason }) => ({ message, members, reason })),
+ [{ message: 'Malformed request', members: [], reason: 'malformedRequest' }]);
} else {
assert.equal(status, 200, JSON.stringify(sorted));
assert.equal(sorted.isSuccess, true);
const expected = name === 'name'
? direction === 'ascending' ? ['alpha', 'bravo', 'charlie'] : ['charlie', 'bravo', 'alpha']
- : name === 'status'
- ? direction === 'ascending' ? ['charlie', 'bravo', 'alpha'] : ['alpha', 'charlie', 'bravo']
- : originalOrder; // Arc.TypeScript#174: distinct complex values currently sort as a no-op.
+ : direction === 'ascending' ? ['charlie', 'bravo', 'alpha'] : ['alpha', 'charlie', 'bravo'];
assert.deepEqual(sorted.data.map(row => row.name), expected, `${family}/${Query.name} ${backend} ${name} ${direction}`);
}
}
diff --git a/ContractTests/README.md b/ContractTests/README.md
index c4e8d6cd..c4488684 100644
--- a/ContractTests/README.md
+++ b/ContractTests/README.md
@@ -43,9 +43,9 @@ Review the inventory alongside `raw.diff`. Entries distinguish `intentional` dif
Known sorting behavior is recorded separately:
- **Known .NET defect — sorting API:** .NET 22.45.0 model-bound generation emits `sortBy.id` from the query parameter instead of the response model. TypeScript follows the documented read-model-field contract, as does .NET controller-based generation. The client sends the field unchanged; `id` is not a `Listing` field and fails on .NET `IQueryable`. This is not an intentional API difference ([Arc#2998](https://github.com/Cratis/Arc/issues/2998)).
-- **Known limitation of both generators — complex fields:** result-oriented helpers include fields such as `detail` and `notice`, even though these objects sort as no-ops on the TypeScript server and throw on .NET `IQueryable`. The model-bound .NET defect masks this limitation in its captured helpers ([Arc.TypeScript#174](https://github.com/Cratis/Arc.TypeScript/issues/174)). Use scalar fields such as `name` and `status` instead.
+- **Known .NET limitation — complex fields:** .NET result-oriented helpers still include fields such as `detail` and `notice`, which throw on .NET `IQueryable`; TypeScript keeps those helpers for compatibility but deprecates them until the [next major release](https://github.com/Cratis/Arc.TypeScript/issues/177), and rejects complex-field in-memory sorting with `malformedRequest` without changing database-provider ordering. The model-bound .NET defect masks this limitation in its captured helpers ([Arc.TypeScript#174](https://github.com/Cratis/Arc.TypeScript/issues/174)). Use scalar fields such as `name` and `status` instead.
-`Notice.ts` agrees after timestamp normalization. Routes, parameter/property descriptors, GUID/date/nested/derived hydration, numeric enum values, portable validation boundaries/messages and all command/query/observable hook signatures are asserted against the pinned client. Hydration uses controlled GET responses. Sorting checks send generated ascending/descending `Sorting` values through the client to the real TypeScript HTTP pipeline over three distinct rows, checking scalar order, complex-field no-ops and invalid-field rejection. Full regeneration additionally starts the locked .NET fixture: its `All` query returns `IQueryable` over the same rows, exposing invalid-field and complex-field exceptions as HTTP 500 results. Positive scalar controls distinguish those failures from an unavailable route or server. These known-behavior assertions must change when the linked issues are fixed; offline runs explicitly skip live .NET checks.
+`Notice.ts` agrees after timestamp normalization. Routes, parameter/property descriptors, GUID/date/nested/derived hydration, numeric enum values, portable validation boundaries/messages and all command/query/observable hook signatures are asserted against the pinned client. Hydration uses controlled GET responses. Sorting checks send generated ascending/descending `Sorting` values through the client to the real TypeScript HTTP pipeline over three distinct rows, checking scalar order and invalid-field rejection, plus deprecated complex-field helpers that TypeScript's in-memory pipeline rejects. Full regeneration additionally starts the locked .NET fixture: its `All` query returns `IQueryable` over the same rows, exposing invalid-field and complex-field exceptions as HTTP 500 results. Positive scalar controls distinguish those failures from an unavailable route or server. These known-behavior assertions must change when the linked issues are fixed; offline runs explicitly skip live .NET checks.
No React tree is mounted. Each output runs in a separate process so derived-type registration cannot leak from the other. A strict consumer includes negative type checks; bundling for the Node runtime resolves extensionless imports without modifying the captured sources.
diff --git a/Documentation/index.md b/Documentation/index.md
index 09d0f47f..2735de70 100644
--- a/Documentation/index.md
+++ b/Documentation/index.md
@@ -8,7 +8,7 @@ Arc for TypeScript is a Node.js server implementation of [Arc](/arc/), the Crati
Without it, a Node.js backend for an Arc frontend means writing every route, request parser, validation response, and status code by hand, then keeping all of it in step with the frontend. With it, commands and queries run through one pipeline that owns those concerns, the wire behavior follows Arc on .NET, and the proxy generator writes the typed frontend client from your source.
:::caution[Source preview, no full parity]
-No package is published to npm; the manifests are at version 0.57.0 for a source preview. Arc for TypeScript does **not** have full parity with Arc on .NET, and package names and APIs can still change. The [capability reference](reference/capabilities.md) is the single place for status and evidence.
+No package is published to npm; the manifests are at version 0.57.1 for a source preview. Arc for TypeScript does **not** have full parity with Arc on .NET, and package names and APIs can still change. The [capability reference](reference/capabilities.md) is the single place for status and evidence.
:::
## What it looks like
diff --git a/Documentation/proxy-generation/generated-code.md b/Documentation/proxy-generation/generated-code.md
index 9edb4740..6d0ead22 100644
--- a/Documentation/proxy-generation/generated-code.md
+++ b/Documentation/proxy-generation/generated-code.md
@@ -77,7 +77,7 @@ export class BooksForAuthor extends ObservableQueryFor` for a snapshot, `ObservableQueryFor` when the method returns an observable source |
| `queryName` | The fully qualified name the server uses for hub subscriptions |
| `defaultValue` | What `result.data` holds before the first answer: `[]` for a list, `{} as TModel` for a single model |
-| `sortBy` | For list results, one sort helper per model field, as a static and an instance property |
+| `sortBy` | For list results, one sort helper per model field, as a static and an instance property; complex-field helpers are deprecated for removal in the next major release |
| `parameterDescriptors`, `requiredRequestParameters` | The arguments, so the client can wait until required ones are set |
| `validation` | A `QueryValidator` when the query's arguments have client-safe rules |
diff --git a/Documentation/proxy-generation/index.md b/Documentation/proxy-generation/index.md
index 8ab60d78..de0da5b1 100644
--- a/Documentation/proxy-generation/index.md
+++ b/Documentation/proxy-generation/index.md
@@ -48,7 +48,7 @@ The repository's [paired-generator comparison](https://github.com/Cratis/Arc.Typ
This is a compatibility check, not a byte-equality promise. Intentional differences include type-only imports, source enum member names (rather than .NET's camel-cased names), formatting and provenance. The pinned .NET model-bound generator's query-parameter sorting helpers are a [known defect](https://github.com/Cratis/Arc/issues/2998), not an intentional API difference: TypeScript follows the documented contract that `sortBy` names a read-model field.
-Both generators have a [known limitation](https://github.com/Cratis/Arc.TypeScript/issues/174) when emitting result-field helpers: complex fields such as `detail` and `notice` are included despite not being sortable scalars. These fixture objects sort as no-ops on the TypeScript server and throw on .NET `IQueryable`. Prefer scalar fields such as `name` and `status`.
+TypeScript retains but deprecates `sortBy` helpers for record, nested-model, array, map, and polymorphic fields until the [next major release](https://github.com/Cratis/Arc.TypeScript/issues/177): in-memory sorting rejects them, database providers apply their own ordering, and you should sort on a scalar field instead; scalar helpers (string, number, boolean, Date, Guid, DateOnly, TimeOnly, TimeSpan, enums, and concepts over those) are unchanged.
The inventory separates intentional differences, known defects and known limitations. Regeneration rejects unreviewed bytes, normalizing only the generated header's timestamp; offline checks also reject changed C# fixture or .NET option fingerprints. See the [contract-test guide](https://github.com/Cratis/Arc.TypeScript/blob/main/ContractTests/README.md#compare-proxy-generators) for the inventory, behavioral sorting checks and recapture instructions.
diff --git a/Documentation/queries/model-bound/paging.md b/Documentation/queries/model-bound/paging.md
index 0119d0cc..4df9e450 100644
--- a/Documentation/queries/model-bound/paging.md
+++ b/Documentation/queries/model-bound/paging.md
@@ -52,7 +52,9 @@ With `Catalog` registered as a singleton, `GET /api/all?pageSize=2&sortBy=name&s
Invalid directions answer 400 with `malformedRequest` and the `sortDirection` (GET) or `sorting.direction` (`QUERY`) member. Page offsets are clamped to the signed 32-bit maximum before slicing in memory, so large valid page and size values cannot overflow the offset. Providers that cut their own pages must apply equivalent bounds before using the offset in their data source.
-In-memory sorting requires the field on every item, or the request answers 400. Dates compare by time, numbers and bigints numerically, `false` before `true`, and `null` or `undefined` before any value in ascending order. Other values compare as strings with `localeCompare`, which is not .NET invariant-culture collation; sort in the data source when a stable cross-platform order matters.
+In-memory sorting requires the field on every item, or the request answers 400. Dates compare by time, numbers and bigints numerically, `false` before `true`, and `null` or `undefined` before any value in ascending order. Other scalar values compare as strings with `localeCompare`, which is not .NET invariant-culture collation; sort in the data source when a stable cross-platform order matters.
+
+In-memory array sorting rejects present, non-null complex values with the existing `malformedRequest` validation result (HTTP 400 for snapshots; a validation result if streaming has already started), without changing provider-owned sorting; raw BSON scalars remain accepted with string comparison.
A query that returns something other than an array answers 400 when the request asks for paging or sorting.
diff --git a/Documentation/reference/packages.md b/Documentation/reference/packages.md
index 9ae6fe0e..ddfd28e0 100644
--- a/Documentation/reference/packages.md
+++ b/Documentation/reference/packages.md
@@ -3,7 +3,7 @@ title: Packages
description: The packages this repository builds, what each exports, their peer dependencies and Node.js requirements, and how they relate to the published @cratis/arc client.
---
-Every package in this repository is at version 0.57.0, the version of the source preview. **None is published to npm.** They ship ES modules only. Clone this repository, run `yarn install` and `yarn build`, and then use the packages in one of two ways:
+Every package in this repository is at version 0.57.1, the version of the source preview. **None is published to npm.** They ship ES modules only. Clone this repository, run `yarn install` and `yarn build`, and then use the packages in one of two ways:
- **Inside the clone.** Put your application in a folder under `Samples/`, which the root `workspaces` list includes, and reference the packages with the `workspace:^` protocol, as [`Samples/Tasks/package.json`](https://github.com/Cratis/Arc.TypeScript/blob/main/Samples/Tasks/package.json) does. `workspace:^` resolves only inside this repository's Yarn workspace.
- **In your own project.** Pack each package you need with `yarn workspace pack --out ` and install the tarballs with npm. Use `yarn pack`: it rewrites `workspace:^` dependencies to version ranges, and `npm pack` does not. `yarn check:consumers` installs packed packages this way to check NodeNext and Bundler consumers.
diff --git a/README.md b/README.md
index 46b5322c..f8830df4 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,7 @@ export class TaskItem {
| `@cratis/arc.chronicle` | [`Source/Chronicle`](Source/Chronicle) | **Experimental.** `builder.withChronicle` appends returned events and resolves registered read models by command key; nested command returns join one event-log batch. In-memory command assertions are available under `@cratis/arc.chronicle/testing`. SDK 6.19.0 imports natively and infers read models from projections/reducers; an opt-in kernel suite covers aggregate replay and reactor commands. Full .NET transaction parity remains unverified. |
| `@cratis/cratis` | [`Source/Cratis`](Source/Cratis) | **Experimental source preview.** `CratisApplication.createBuilder()` and `builder.addCratis()` compose Arc and a Chronicle client without installing authentication; not yet published to npm. |
-Every package manifest is at version 0.57.0. That is the version of this source preview, not an npm release, and the Chronicle package is experimental. The packages ship ES modules only, and schemas use Zod 4. Core, host adapters, MongoDB, and Drizzle need Node.js 22 or later. The Fetch entry has a neutral bundle with `node:async_hooks` as its only Node import; its command, query, and SSE paths run in a Next.js App Router route handler on the Node.js runtime, with Bun and Deno smoke checks; Cloudflare Workers and the Next.js Edge runtime are not supported. See [Fetch API runtimes](Documentation/hosts/fetch-runtimes.md). The root workspace needs Node.js 22.19 or later, because it installs the Chronicle SDK; Node.js 24 LTS is recommended.
+Every package manifest is at version 0.57.1. That is the version of this source preview, not an npm release, and the Chronicle package is experimental. The packages ship ES modules only, and schemas use Zod 4. Core, host adapters, MongoDB, and Drizzle need Node.js 22 or later. The Fetch entry has a neutral bundle with `node:async_hooks` as its only Node import; its command, query, and SSE paths run in a Next.js App Router route handler on the Node.js runtime, with Bun and Deno smoke checks; Cloudflare Workers and the Next.js Edge runtime are not supported. See [Fetch API runtimes](Documentation/hosts/fetch-runtimes.md). The root workspace needs Node.js 22.19 or later, because it installs the Chronicle SDK; Node.js 24 LTS is recommended.
## Try it
diff --git a/Source/Chronicle/package.json b/Source/Chronicle/package.json
index afe6ed75..afddbc51 100644
--- a/Source/Chronicle/package.json
+++ b/Source/Chronicle/package.json
@@ -1,6 +1,6 @@
{
"name": "@cratis/arc.chronicle",
- "version": "0.57.0",
+ "version": "0.57.1",
"publishConfig": {
"access": "public"
},
@@ -34,8 +34,8 @@
"README.md"
],
"peerDependencies": {
- "@cratis/arc.core": "^0.57.0",
- "@cratis/arc.testing": "^0.57.0",
+ "@cratis/arc.core": "^0.57.1",
+ "@cratis/arc.testing": "^0.57.1",
"@cratis/chronicle": "^6.29.0",
"@cratis/fundamentals": "^7.19.6",
"rxjs": "^7.8.2",
diff --git a/Source/CodeAnalysis/Version.ts b/Source/CodeAnalysis/Version.ts
index 8e30aa66..8c2256c4 100644
--- a/Source/CodeAnalysis/Version.ts
+++ b/Source/CodeAnalysis/Version.ts
@@ -1,4 +1,4 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// Generated by yarn set-version. Do not edit.
-export const packageVersion = '0.57.0';
+export const packageVersion = '0.57.1';
diff --git a/Source/CodeAnalysis/package.json b/Source/CodeAnalysis/package.json
index d33b194a..576f4f07 100644
--- a/Source/CodeAnalysis/package.json
+++ b/Source/CodeAnalysis/package.json
@@ -1,6 +1,6 @@
{
"name": "@cratis/eslint-plugin-arc-core",
- "version": "0.57.0",
+ "version": "0.57.1",
"type": "module",
"license": "MIT",
"description": "ESLint diagnostics for Arc for TypeScript server artifacts",
diff --git a/Source/Core/Version.ts b/Source/Core/Version.ts
index 8e30aa66..8c2256c4 100644
--- a/Source/Core/Version.ts
+++ b/Source/Core/Version.ts
@@ -1,4 +1,4 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// Generated by yarn set-version. Do not edit.
-export const packageVersion = '0.57.0';
+export const packageVersion = '0.57.1';
diff --git a/Source/Core/package.json b/Source/Core/package.json
index 7781c918..9b79e298 100644
--- a/Source/Core/package.json
+++ b/Source/Core/package.json
@@ -1,6 +1,6 @@
{
"name": "@cratis/arc.core",
- "version": "0.57.0",
+ "version": "0.57.1",
"type": "module",
"license": "MIT",
"publishConfig": {
diff --git a/Source/Core/queries/for_queryRendering/given/a_sorted_client_output_server.ts b/Source/Core/queries/for_queryRendering/given/a_sorted_client_output_server.ts
new file mode 100644
index 00000000..4bfde6e3
--- /dev/null
+++ b/Source/Core/queries/for_queryRendering/given/a_sorted_client_output_server.ts
@@ -0,0 +1,57 @@
+// Copyright (c) Cratis. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+import { z } from 'zod';
+import { ArcServer } from '../../../ArcServer.js';
+import { ServiceLifetime } from '../../../dependencyInjection/ServiceLifetime.js';
+import { serviceToken } from '../../../dependencyInjection/ServiceToken.js';
+import type { ClientContract } from '../../../introspection/ClientContract.js';
+import { defineQuery } from '../../defineQuery.js';
+import { defineObservableQuery } from '../../observable/defineObservableQuery.js';
+import { CurrentValueSubject } from '../../observable/CurrentValueSubject.js';
+import type { ReadModelInterceptor } from '../../ReadModelInterceptor.js';
+import { markRawReadModelDocument } from '../../rawReadModelDocuments.js';
+
+class Secret {
+ constructor(readonly order: number, readonly name: string) {}
+}
+
+export class a_sorted_client_output_server {
+ readonly seen: number[] = [];
+ readonly server: ArcServer;
+ constructor(raw: boolean, release: boolean) {
+ const released = new WeakSet