# DynamicData ↔ RxJS compatibility

This package implements keyed reactive caches, ordered reactive lists, and a broad set of DynamicData-style operators using actual RxJS observables. It exposes lower-camel-case functions and PascalCase aliases where the name is not occupied by a class, with fluent methods on the package's decorated observables. Its web contracts are described below. It does **not** establish complete C# overload, type-member, or change-trace parity.

The reference is DynamicData commit [`ef790af138811c3268c9bb687886c2acbd0b66c0`](https://github.com/reactivemarbles/DynamicData/tree/ef790af138811c3268c9bb687886c2acbd0b66c0), whose version pattern is `10.0-preview.{height}`. The package uses RxJS 7.8.x in place of the reference project's System.Reactive 6.1.0.

## How to read the API inventory

All **118 distinct names** declared across upstream `ObservableCacheEx` and `ObservableListEx` have a mapped web counterpart in the source-module snapshot used for this document. Those names correspond to **437 upstream overload declarations**: 109 cache names/313 declarations and 63 list names/124 declarations, with many names shared by both owners. These are API inventory counts, not counts of independently verified features or compatible overloads.

The complete public source inventory is wider than those two extension classes. It contains 183 static method names, 680 declarations, and 144 public type identities. The type count includes 44 interfaces, 24 binding-area types, three legacy platform types, two experimental types, public test-support helpers, static extension classes, and separate generic arities such as `Change<T>` and `Change<T,K>`. It would be misleading to turn a ratio of matching type names into a feature-completeness percentage. A TypeScript interface, runtime class, alias, or metadata object can represent part of a .NET type contract without reproducing every member or inheritance relationship.

[`UPSTREAM-API.md`](UPSTREAM-API.md) and [`upstream-api.json`](upstream-api.json) define the pinned baseline. [`API-MAPPING.md`](API-MAPPING.md) and [`api-mapping.json`](api-mapping.json) give the latest generated availability snapshot, including package-entrypoint import errors, if any. Their `adapted` status means a matching web name or explicitly mapped member exists; it does not certify all overloads. Run `node docs/generate-api-mapping.mjs` after changing exports or declarations. That audit checks `src`, `types`, and root declaration files.

## Implemented families and overload adaptations

The seven core rows below partition the 118 names for navigation; each name is counted once. Aggregation and alias helpers are additional upstream extension families.

| Family | Core names | Implemented web behavior | Overload adaptations and practical limits |
| --- | ---: | --- | --- |
| Source editing | 6 | Add/update, remove, clear, refresh, and edit-diff operations through source collections and convenience helpers. | JavaScript arguments select object/key/iterable cases at runtime. `SourceCache` uses a key-selector function. C# comparer, key-value-pair, and source-interface overloads require the documented web shape; their existence upstream does not imply every call form is accepted. |
| Filtering, projection, and key mapping | 23 | Static/dynamic/state filtering; immutable filtering; sync, async, safe, inline, and observable transforms; flattening; distinct values; casts; key conversion. | C# generic overloads become selector functions/options. The cache transform callback is `(item, key, previousSource)` and the list form receives `(item, index, previousProjection)`; these adapt the C# overloads and optional-value argument order. Async transforms pass `(item, key, previous, AbortSignal)` and accept `maximumConcurrency` and `transformOnRefresh`. `cast` accepts a conversion function; it does not enforce CLR generic cast rules. |
| Groups, joins, and trees | 15 | Live/immutable groups, property-driven and observable groups, specified-result-group control, all four join variants and their many forms, parent/child trees. | Join selectors accept `(left, right)` or `(key, left, right)` according to function arity. Outer joins use `Optional`; inner composite keys are stable frozen JavaScript arrays carrying `leftKey`/`rightKey`. List groups retain stable occurrence identities and emit indexed child insert/remove/move/replace/refresh changes, preserving unaffected item subscriptions. Notification compression may still differ from upstream. Per-item observable grouping currently operates on keyed cache streams. |
| Sorting, windows, and binding | 12 | Stable sorting, comparer changes, paging, virtual windows, top/reverse views, target binding, sorted collections, index updates. | Comparers are functions or `compare`/`Compare` objects; request objects use page/size or startIndex/size. `top(size)` applies to the incoming order; `top(comparer, size)` supplies the comparer-taking form. Binding takes an explicit target, callback, or supported collection instead of C# `out` arguments. `.NET` collection notifications, reset policies, and context types are adapted to JavaScript change metadata. |
| Combination, materialization, and queries | 20 | Set algebra, static/dynamic source combinations, owned child merges, item observables, switch, reference-counted replay, population, snapshots, key watching. | Merge overloads use JavaScript arrays/observables and options such as `comparer`, `equalityComparer`, and `completable`. Per-source ownership retracts removed input contributions and restores an available contributor. `refCount` replays the current collection to late subscribers rather than only the previous delta. Supported factory/call forms are those exported by this package, not every C# overload. |
| Time, properties, and resource lifecycle | 18 | Auto-refresh, observable predicates, per-item subscriptions/disposal, async disposal completion, batching/gates, initial buffering, expiration/size limits, status/property streams. | Durations are milliseconds and scheduler arguments are RxJS `SchedulerLike`. `whenChanged` takes a property array and result selector. `AsyncDisposeMany` uses a separate completion observable for asynchronous cleanup. Property observations require the explicit web notification bridge. Expiration/retention source-versus-stream behavior is detailed below. |
| Change utilities and observation | 24 | Change-reason filtering, update suppression, clone/adapt, flattening, load gates, start values, item/change callbacks, refresh suppression, index removal, move conversion. | Changes are JavaScript records/arrays with string reasons and explicit indexes. C# optional and framework object shapes are translated. A matching utility name does not guarantee identical batching, diagnostic counts, inherited interfaces, or event type identities in all upstream contexts. |
| Aggregation | Additional | Count/empty tests, sum, average, extrema, standard deviation, aggregate change streams, and invalidation. | One JavaScript numeric contract folds numerous `int`, `long`, `float`, `double`, `decimal`, and nullable overloads. Numeric precision and the distinction between `stdDev` and `standardDeviation` are described below. |
| Query aliases and binding helpers | Additional | `Select`/`Where`-style aliases, observable property/collection adapters, comparer builders, and request objects. | Aliases route to web operators. Property/collection adapters do not implement the actual .NET notification interfaces or parse C# expression trees. Refer to the generated mapping for optional, enumerable, test-support, experimental, and platform helpers still lacking a named counterpart. |

## Important behavioral contracts

**RxJS interoperability and fluent calls.** Pipeable operators consume and return RxJS observables, so ordinary RxJS scheduling, errors, completion, subscriptions, and testing apply. PascalCase operator aliases are JavaScript pipeable functions, not C# extension syntax. The exported `Group` name is the group class; use `group(selector)` as the standalone operator or `.Group(selector)` on a decorated stream. Fluent decoration is installed for package-produced connections and explicit decorated observables; it does not patch every RxJS observable globally. A source mutation helper and a stream operator can require different call shapes despite sharing a familiar name.

**Collection identity and equality.** Keyed caches use JavaScript map keys. Object keys compare by identity; custom .NET hash comparers and arbitrary structural key equality do not transfer automatically. Lists retain separate occurrences, including repeated identical references. Use the dedicated list change form for indexed operations. Derived list operators may expand ranges/clears into individual changes, so emitted traces can differ even when the resulting list matches. Cache/list reason enums expose string values (`add`, `update`, `remove`, `refresh`, `move`, and list range/replace reasons), whereas upstream enums use numeric values. Serialized C# change streams therefore require an adapter.

**Previous values and selector arguments.** Web records normally expose raw lower-camel `previous` values, while compatibility accessors can supply `Optional`. `Optional` distinguishes absence from an explicitly present `undefined`, `null`, `false`, or zero. Callbacks follow their JavaScript signatures; do not copy three-argument C# callbacks without checking argument order. In particular, `transform`, async transforms, join selectors, inline update actions, and safe-error callbacks have different adaptation points.

**Mutable properties.** `createObservableObject` is a shallow proxy. Keep and mutate the returned proxy, or call `notifyPropertyChanged` explicitly. Mutating an ordinary object emits no event. Dotted property paths subscribe at each bridged segment and rewire when a parent changes. Mutations inside a nested plain object still require a corresponding notification or a supplied observable proxy. Function selectors are reevaluated on relevant bridge events, not through automatic dependency tracking.

**Expiration and retention.** `source.connect().pipe(expireAfter(...))` and `limitSizeTo(...)` govern the downstream stream. They do not remove entries from the original `SourceCache` or `SourceList`. Explicit source-mutating overloads are also available: `expireAfter(source, selector, options)` and `limitSizeTo(source, size, options)`. Their subscriptions remove backing-source items and emit arrays of removed entries. `toObservableChangeSet` retention removes entries from its own backing cache/list, so its configured bounds apply to retained source data. List retention keeps source occurrence/index bookkeeping while suppressing evicted output, so it should not be treated as a bound on the original list's memory use.

**Asynchronous work.** Async transforms remove queued jobs when their items disappear, bound started jobs with `maximumConcurrency`, abort the supplied signal on replacement/removal, and reject stale results from output. Aborting a signal does not forcibly cancel an arbitrary Promise; the caller's work must observe it. Observable/Promise concurrency is not CPU parallelism. The legacy `.NET` `ParallelOperators`, thread-pool behavior, and framework synchronization primitives do not have a worker-based equivalent here.

**Sort/window/binding options.** Ordered views carry metadata such as `items`, `keys`, `sortedItems`, `response`, and `context`. Combined sort/window operators expose context containing the response, comparer, options, and request, and wait for the first request/comparer when those are observables. Standalone `page`/`virtualise` begin with their default request. These objects represent web results, not every property/member of upstream `ISortedChangeSet`, `PageContext`, or `VirtualContext`. `BindingOptions` and related option objects do not imply that every upstream reset-threshold or adapter policy has been reproduced. Bound arrays preserve their identity, but their content mutation/event granularity differs from .NET observable collections. Convenience request constructors accept positive window sizes; upstream zero-sized/empty sentinels are not interchangeable with them. Sorting supports reset thresholds and observable resort requests, but not every upstream optimization flag, initial-capacity setting, scheduler overload, or option-record type.

**Group traces.** Keyed groups update their child caches incrementally, and observable groups wait for the first per-item grouping value. List grouping recomputes partitions but reconciles each child group by stable occurrence identity. It preserves unaffected child resources and publishes targeted indexed changes. Partitioning remains O(n), and an arbitrary large reorder can take O(n²) reconciliation work. Equivalent states do not imply identical upstream notification compression.

**Numeric aggregation.** Aggregators use JavaScript `number`; exact `decimal` arithmetic, 64-bit integer precision, CLR overflow semantics, and every nullable-overload rule are not reproduced. Empty/default handling uses the web function's documented arguments. For compatibility with the pinned implementation, `stdDev` uses `sqrt(max(0, Σx² − (Σx)²/n)) / (n − 1)` when there are at least two contributing values. The additional `standardDeviation` operator uses the conventional sample formula `sqrt(max(0, Σx² − (Σx)²/n) / (n − 1))`. They are intentionally separate APIs.

## Performance scope

Unsorted cache filter/transform paths process affected keys and avoid rerunning selectors for unchanged records. Sorted insertion can use binary search, but array movement, ordered snapshot metadata, and changed windows still have collection-size costs. List transforms, list grouping, flattening, distinct views, and some reconciliation paths scan retained entries; reordering can require repeated index lookup/splice work. There is no general guarantee that every operator costs only the number of incoming changes.

The implementation chunks large range insertions to avoid JavaScript argument-count limits. Large collection support and a virtualized sample table do not imply constant-memory streams, worker execution, or uniform performance for every operator combination. Use the supplied benchmarks to assess source population, individual edits, sort changes, and DOM rendering separately. Benchmark results describe their recorded environment and workload.

## Verification and remaining work

The tests in `test/core.test.js`, `test/operators.test.js`, `test/advanced.test.js`, `test/lifecycle.test.js`, and `test/extras.test.js` cover source transactions, duplicate list occurrences, change replay, filter/projection transitions, sorted windows, grouping/join ownership, timers, cancellation/cleanup, and utility behavior. They are targeted JavaScript regressions; the full upstream C# fixture suite has not been mechanically translated or differentially executed against this package.

The remaining compatibility work includes overload-by-overload differential tests, identical change-batch compression, full expression-tree property dependency tracking, the full .NET collection binding/context option contract, numeric-type fidelity where needed, and helper/type members marked missing in the generated mapping. Platform thread/parallel APIs require a separately designed web worker contract. Export aliases and interface declarations should not be used to close those items without exercising their behavior.

## Native ReactiveWeb interoperability (0.1.1)

The property operators recognize structural RxJS `Changed`, `PropertyChanged`
and lowercase equivalents in addition to the port's observable-object proxies.
ReactiveWeb `PropertyName`/`Value`/`OldValue` events and unspecified-property
notifications refresh the matching subscriptions. Nested paths rewire when an
intermediate object is replaced, and aliases do not duplicate subscriptions.
Lifecycle resources may expose `.Dispose()` or `.DisposeAsync()` as well as
JavaScript/RxJS disposal methods.

`Bind` forwards the original batch to targets exposing `ApplyChanges` or
`applyChanges`, which lets ReactiveWeb retain indexed changes and object
lifetimes. PascalCase `Edit`/`Clear`/`AddRange` collection targets are also
recognized. These structural hooks do not add a dependency on ReactiveWeb.

`test/reactive-interop.test.js` and `test/reactive-interop-types.ts` verify these
contracts, cleanup after synchronous errors and shared public helper identity.
