Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ContractTests/Client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cratis/arc.core-client-contract",
"version": "0.25.0",
"version": "0.26.0",
"private": true,
"type": "module",
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion Documentation/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.25.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.26.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.
:::

## What it looks like
Expand Down
36 changes: 36 additions & 0 deletions Documentation/mongodb/change-stream-watcher.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
title: Watch MongoDB changes across collections
description: React to tenant-scoped database changes with a shared stream and bounded observation lifetimes.
---

When several parts of your application need to respond to MongoDB changes, opening a separate change stream for each one is wasteful. Resolve `mongoDBWatcher` in the same Arc scope as your collections. The watcher shares one **database-level** change stream among subscriptions in that scope, and routes collection changes to each subscriber.

This is a source-preview API. It is not a durable event consumer or a replacement for Chronicle.

## React to changes

The example assumes a configured Arc application, an active tenant scope, and registered `Book` and `Author` models:

```typescript
import { mongoCollection, mongoDBWatcher } from '@cratis/arc.mongodb';

const books = await scope.resolve(mongoCollection(Book));
const watcher = await scope.resolve(mongoDBWatcher);
const subscription = watcher.changes(books).subscribe(change => {
console.log(change.operationType, change.documentKey);
});

// On shutdown or when this work ends:
subscription.unsubscribe();
await scope.dispose();
```

`changes` returns an RxJS `Observable<ChangeStreamDocument<Document>>`. Updates use MongoDB's `updateLookup` full-document option; deletes have a document key but no full document. Do not persist a resume token from this API or use it as an exactly-once feed.

## Scope and failure

MongoDB change streams require a replica set or sharded cluster and database-level watch permissions. A standalone server fails before the initial read. Join only collections resolved from the **same tenant and Arc scope** as the watcher; a cross-scope or cross-tenant collection is rejected. Disposing the scope, aborting its signal, or unsubscribing the last listener closes the cursor. A later subscription opens a new stream and starts at a new operation time.

The driver resumes errors it recognizes as resumable. Other errors terminate all subscribers with `error`; the watcher does **not** silently reconnect and skip an unknown interval. Resubscribe with a fresh scope and re-read authoritative state if you need recovery. The watcher holds no durable checkpoint.

For a live combined result, use [joined observation](joined-observe.md). For a single collection's complete snapshots, [observe on the collection](observing-collections.md) instead.
53 changes: 53 additions & 0 deletions Documentation/mongodb/geospatial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
title: Store GeoJSON with Fundamentals types
description: Encode Point, LineString, and Polygon fields as MongoDB GeoJSON and restore typed geometry.
---

Use the geospatial types from `@cratis/fundamentals/geospatial` when a MongoDB read model stores locations, routes, or areas. `MongoDocumentCodec` writes their geometry as GeoJSON without changing driver-wide BSON serialization.

## Point: one location

Declare `@field(Point)` on the model. Longitude comes **first**, then latitude:

```typescript
import { field } from '@cratis/fundamentals';
import { Point } from '@cratis/fundamentals/geospatial';
import { key } from '@cratis/arc.core';

class Place {
@field(String) @key() id!: string;
@field(Point) location!: Point;
}

const place = Object.assign(new Place(), { id: 'one', location: new Point(10, 20) });
// collection.codec.serialize(place).location -> { type: 'Point', coordinates: [10, 20] }
```

MongoDB can index the stored `location` field with a `2dsphere` index. Creating indexes and composing `$near` filters are application responsibilities; the Fundamentals `Point` is **not** a driver `GeoJSON` filter-builder argument.

## LineString: a route

A `LineString` holds two or more `Point`s. The codec writes `{ type: 'LineString', coordinates: [[10, 20], [30, 40]] }` for:

```typescript
import { LineString, Point } from '@cratis/fundamentals/geospatial';

const route = new LineString([new Point(10, 20), new Point(30, 40)]);
```

Declare the field with `@field(LineString)`. Typed reads restore `LineString` and `Point` instances, not plain arrays.

## Polygon: an area, optionally with holes

A polygon stores the outer shell first and then its interior rings. Each `LinearRing` needs at least four points; repeat the first point as the last:

```typescript
import { LinearRing, Point, Polygon } from '@cratis/fundamentals/geospatial';

const shell = new LinearRing([
new Point(10, 20), new Point(30, 20), new Point(30, 40), new Point(10, 20)
]);
const area = new Polygon(shell);
```

Declare `@field(Polygon)` on the model. The BSON field contains `{ type: 'Polygon', coordinates: [/* shell coordinates, then holes */] }`; reads reconstruct `Polygon` and its `LinearRing`s. The codec rejects an unclosed ring, non-finite coordinates, a mismatched GeoJSON type, or missing coordinates rather than returning a misleading geometry. It does not enforce winding order, non-intersection, or MongoDB's full spatial-index rules. Validate domain geometry before writing, and use stored MongoDB field names in your own spatial filters. See [serializers](serializers.md) for the other model types and [naming policies](naming-policies.md) for stored names.
5 changes: 4 additions & 1 deletion Documentation/mongodb/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ Your read models live in MongoDB, and every tenant has its own database. Wiring
| Match Arc on .NET's property and collection naming | [Naming policies](naming-policies.md) |
| Count, sort, and page in the database | [Paging](paging.md) |
| Turn a change stream into an observable query | [Observing collections](observing-collections.md) |
| Combine two or three live collections | [Joined observation](joined-observe.md) |
| React to raw collection changes | [Change-stream watcher](change-stream-watcher.md) |
| Store GeoJSON Point, LineString, and Polygon fields | [Geospatial types](geospatial.md) |
| Load a read model by command key | [Command context](../commands/command-context.md#load-a-read-model-by-key) |

`withMongoDB` registers a read-model resolver for the models you list in `readModels`, so a command can declare `@inject(commandReadModel(TaskRecord))` and receive the document whose identity equals the command key. Do not also register another integration, such as Chronicle, as the owner of the same type; `build()` fails when two claim one type.
Expand All @@ -33,6 +36,6 @@ The original `MongoReadModels<T, I>` remains for low-level `defineQuery` users.

## Current boundaries

This integration does not supply transactions, a shared watcher or reconnect policy, joined observations, geospatial serializers, resilience middleware, or driver metrics. Do not infer any of those from Arc on .NET. The [capability reference](../reference/capabilities.md#persistence-and-chronicle) has the parity details.
This integration does not supply cross-store transactions or a durable change-stream checkpoint. The watcher shares a stream **within a tenant scope**, not across the process. Recognized transient reads retry at most twice; writes are not retried. Arc-owned clients expose OpenTelemetry MongoDB metrics, but caller-owned clients are not instrumented. Do not infer .NET's process-wide watcher or general-purpose resilience interceptors from these narrower guarantees. The [capability reference](../reference/capabilities.md#persistence-and-chronicle) has the parity details.

Start with [Get started](getting-started.md).
27 changes: 27 additions & 0 deletions Documentation/mongodb/joined-observe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: Observe joined MongoDB collections
description: Combine current tenant-scoped collections and re-emit the result when either changes.
---

A catalog can depend on books **and** their authors. Observing only books leaves the catalog stale when an author changes. Resolve the scoped watcher, join the two collections, and select the result you want to publish:

```typescript
import { mongoCollection, mongoDBWatcher } from '@cratis/arc.mongodb';

const books = await scope.resolve(mongoCollection(Book));
const authors = await scope.resolve(mongoCollection(Author));
const watcher = await scope.resolve(mongoDBWatcher);
const catalog = watcher.observe(books, { published: true })
.join(authors, { active: true })
.select((publishedBooks, activeAuthors) => ({ publishedBooks, activeAuthors }));

const subscription = catalog.subscribe(value => console.log(value));
// When the consumer is done:
subscription.unsubscribe();
```

The example is a service fragment: register both models with `withMongoDB({ readModels: [Book, Author], ... })`, and resolve the scope under a trusted tenant identity. The filters are **MongoDB filters in stored field names**, not predicates; use trusted values. See [naming policies](naming-policies.md#names-in-your-own-filters).

`select` returns an RxJS `Observable<TResult>`, rather than .NET's `ISubject<TResult>`. It first emits a combined snapshot, then recomputes it after a change to either collection. Call `.join(thirdCollection, filter?)` before `select` to combine three collections. Every change in a watched collection triggers a refetch, even when the changed document does not match a filter. Consecutive changes while a read is in progress are coalesced into another complete snapshot, not buffered without a bound. Do not treat emissions as an audit trail or a transactionally consistent view across collections.

The per-collection `maxObservableItems` cap applies to **each** joined snapshot. If a read, selector, or change stream fails, the observable errors instead of sending a partial list. The watcher shares one database-level stream per scope; subscriptions close on unsubscribe or scope disposal. Never keep a tenant-scoped collection in a singleton. See [change-stream watcher](change-stream-watcher.md) for recovery limits and [single-collection observation](observing-collections.md) when joins are unnecessary.
3 changes: 2 additions & 1 deletion Documentation/mongodb/observing-collections.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Each emission then holds the first ten tasks by title, and `paging.totalItems` c

- A full list is capped at 1,000 documents by default; `maxObservableItems` raises the cap to at most 10,000. A list that exceeds the cap, on the first read or on any later one, fails the subscription instead of sending a partial list.
- A standalone MongoDB server is rejected with `MongoDB observe requires a replica set with change streams`. Replica sets and sharded clusters support change streams.
- Joined observation across collections is not available.
- For several collections, use [joined observation](joined-observe.md); each collection has its own snapshot cap.

## When the stream fails

Expand All @@ -81,3 +81,4 @@ To recover, subscribe again. A new subscription opens a new change stream and re
- [Observable queries](../queries/observable-queries.md)
- [Paging](paging.md)
- [MongoDB](index.md)
- [Change-stream watcher](change-stream-watcher.md)
6 changes: 6 additions & 0 deletions Documentation/mongodb/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@
href: paging.md
- name: Observing collections
href: observing-collections.md
- name: Joined observation
href: joined-observe.md
- name: Change-stream watcher
href: change-stream-watcher.md
- name: Geospatial types
href: geospatial.md
2 changes: 1 addition & 1 deletion Documentation/reference/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Evidence paths are relative to the repository root. Spec folders follow `for_<Su

| Capability | Status | TypeScript contract | Evidence |
| --- | --- | --- | --- |
| [MongoDB](../mongodb/index.md) | Bounded | Tenant-scoped collections with BSON mapping, .NET-compatible naming policies, database-side paging, change-stream observation, and command read models; `Cratis:MongoDB:{Server,Database}` configuration binding. No transactions, joined observation, reconnect policy, geospatial serializers, resilience middleware, or driver metrics. See [how it is checked](#mongodb-checks). | `Source/MongoDB/for_MongoCollection`, `.../for_MongoDocumentCodec`, `.../for_MongoReadModels`, `.../for_withMongoDB`, `bash Source/MongoDB/run-integration.sh` (live replica set, Docker) |
| [MongoDB](../mongodb/index.md) | Bounded | Tenant-scoped collections with BSON mapping, .NET-compatible naming policies, database-side paging, change streams, command read models, [joined observation](../mongodb/joined-observe.md), [scoped watcher](../mongodb/change-stream-watcher.md), [GeoJSON geometry](../mongodb/geospatial.md), bounded transient read retries, and MongoDB driver metrics for Arc-owned clients. `Cratis:MongoDB:{Server,Database}` configuration binding. No cross-store transactions or durable watcher checkpoint; nonresumable stream failures terminate subscriptions. .NET's process-wide watcher, general-purpose resilience interceptors, and comprehensive metrics for supplied clients are not implemented. See [how it is checked](#mongodb-checks). | `Source/MongoDB/for_MongoCollection`, `.../for_MongoDocumentCodec`, `.../for_MongoReadModels`, `.../for_withMongoDB`, `bash Source/MongoDB/run-integration.sh` (live replica set, Docker) |
| [SQL with Drizzle](../sql/index.md) | Bounded | Tenant-scoped Drizzle handles, column codecs, and SQL count, sort, and page for model-bound queries. SQLite and PostgreSQL tested with real databases; MySQL not run against a live server. No observation, migrations, change tracking, transactions, or command read models. See [how it is checked](#sql-checks). | `Source/Drizzle/for_DrizzleReadModels`, `.../for_ColumnCodec`, `.../for_DrizzleModelCodec`, `.../for_withDrizzle`, `bash Source/Drizzle/run-integration.sh` (live PostgreSQL 16, Docker) |
| [Chronicle](../chronicle/index.md) | Experimental | Not published to npm. `withChronicle` appends returned model-bound events through a response value handler, with routing, subject, and causation resolved from the command; resolves Chronicle read models by command key and in validators; batches nested returned events; and executes Arc commands returned from reactors through the SDK 6.7.0 result hook. SDK 6.7.0 loads in native Node ESM. Keyed aggregates and returned reactor commands are experimental; full .NET parity is unverified. See [how it is checked](#chronicle-checks). | `Source/Chronicle/for_ChronicleResponseHandler`, `.../for_ChronicleUnitOfWork`, `.../for_ChronicleReadModelForCommandResolver`, `.../for_reactorCommandResultHandler`, `.../for_AggregateRoot`, `.../for_ChronicleCommandScenario`, `bash Source/Chronicle/run-integration.sh` (live kernel, Docker) |
| [Chronicle compliance](../chronicle/compliance.md) | Bounded | Subject resolution on appends and `@notAudited` and `@pii` exclusion from the causation chain are supported. Releasing encrypted read-model values on queries or in command read models is not implemented. | `Source/Chronicle/for_ChronicleResponseHandler/when_returning_an_event/with_sensitive_command_fields.ts`, `Source/Chronicle/testing/for_ChronicleCommandScenario` |
Expand Down
2 changes: 1 addition & 1 deletion Documentation/reference/packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.25.0, the version of the source preview. **None is published to npm**; reference them from a clone with the `workspace:^` protocol. They ship ES modules only.
Every package in this repository is at version 0.26.0, the version of the source preview. **None is published to npm**; reference them from a clone with the `workspace:^` protocol. They ship ES modules only.

## Server packages

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.7.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.25.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. The default core entry, host adapters, MongoDB, and Drizzle packages 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 ran in Deno 2.9.7, while Bun, Cloudflare Workers, and Next.js deployments remain unverified. 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.26.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. The default core entry, host adapters, MongoDB, and Drizzle packages 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 ran in Deno 2.9.7, while Bun, Cloudflare Workers, and Next.js deployments remain unverified. The root workspace needs Node.js 22.19 or later, because it installs the Chronicle SDK; Node.js 24 LTS is recommended.

## Try it

Expand Down
6 changes: 3 additions & 3 deletions Source/Chronicle/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cratis/arc.chronicle",
"version": "0.25.0",
"version": "0.26.0",
"publishConfig": {
"access": "public"
},
Expand Down Expand Up @@ -34,8 +34,8 @@
"README.md"
],
"peerDependencies": {
"@cratis/arc.core": "^0.25.0",
"@cratis/arc.testing": "^0.25.0",
"@cratis/arc.core": "^0.26.0",
"@cratis/arc.testing": "^0.26.0",
"@cratis/chronicle": "^6.7.0",
"@cratis/fundamentals": "^7.19.6",
"rxjs": "^7.8.2",
Expand Down
2 changes: 1 addition & 1 deletion Source/CodeAnalysis/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cratis/eslint-plugin-arc-core",
"version": "0.25.0",
"version": "0.26.0",
"type": "module",
"license": "MIT",
"description": "ESLint diagnostics for Arc for TypeScript server artifacts",
Expand Down
2 changes: 1 addition & 1 deletion Source/Core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cratis/arc.core",
"version": "0.25.0",
"version": "0.26.0",
"type": "module",
"license": "MIT",
"publishConfig": {
Expand Down
2 changes: 1 addition & 1 deletion Source/Cratis/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cratis/cratis",
"version": "0.25.0",
"version": "0.26.0",
"type": "module",
"license": "MIT",
"description": "Arc and experimental Chronicle composition for Node.js",
Expand Down
4 changes: 2 additions & 2 deletions Source/Drizzle/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cratis/arc.drizzle",
"version": "0.25.0",
"version": "0.26.0",
"type": "module",
"license": "MIT",
"publishConfig": {
Expand Down Expand Up @@ -29,7 +29,7 @@
"README.md"
],
"peerDependencies": {
"@cratis/arc.core": "^0.25.0",
"@cratis/arc.core": "^0.26.0",
"@cratis/fundamentals": "^7.19.6",
"drizzle-orm": "^0.45.0"
},
Expand Down
2 changes: 1 addition & 1 deletion Source/Express/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cratis/arc.express",
"version": "0.25.0",
"version": "0.26.0",
"type": "module",
"license": "MIT",
"publishConfig": {
Expand Down
Loading
Loading