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.37.0",
"version": "0.38.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.37.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.38.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
2 changes: 1 addition & 1 deletion Documentation/queries/observable-queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ curl -N -H 'Accept: text/event-stream' \

With two tasks registered, each frame holds the second task in descending title order, and `paging` reports `{"page":1,"size":1,"totalItems":2,"totalPages":2}`. When a third task arrives, the next frame is sorted and cut again, and `totalItems` follows the whole list. A generated client's `useWithPaging(pageSize)` hook sends the same parameters.

Arc pages the arrays your source emits, in memory. An observable query cannot return a `queryPage`; generated metadata rejects that declaration at build. For a collection too large to emit whole, narrow what the source emits with query arguments, or use a database integration that observes a query, such as [MongoDB change streams](../mongodb/observing-collections.md). The [paging rules](model-bound/paging.md#request-parameters) for invalid sizes and sort fields are the same as for snapshots.
Arc pages the arrays your source emits, in memory. Model-bound observable queries cannot return a `queryPage`; generated metadata rejects that declaration at build, and generated clients do not support observable-page results. Drizzle model-bound queries use `observe()` for complete small arrays, as MongoDB does. For larger collections, narrow the source with query arguments. Low-level `defineObservableQuery` without generated metadata or proxies can emit a `QueryPage` (including Drizzle's `observePage`); Core validates and streams each page, but the generated client cannot consume this shape. The [paging rules](model-bound/paging.md#request-parameters) for invalid sizes and sort fields are the same as for snapshots.

## Authorize a live query

Expand Down
5 changes: 3 additions & 2 deletions Documentation/reference/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,16 @@ 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 streams, and command read models. No cross-store transactions. 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, PostgreSQL 16, and MySQL 8.4 tested with real databases. MySQL live coverage includes tenant routing, codecs, stable paging, limits, sort rejection, and command lookup. PostgreSQL command lookup with node-postgres covers tenant routing, typed keys, and missing rows. No observation, migrations, change tracking, or transactions. See [how it is checked](#sql-checks). | `Source/Drizzle/for_DrizzleReadModelForCommandResolver`, `Source/Drizzle/for_DrizzleReadModels`, `.../for_ColumnCodec`, `.../for_DrizzleModelCodec`, `.../for_withDrizzle`, `bash Source/Drizzle/run-integration.sh` (live PostgreSQL 16 and MySQL 8.4, 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, PostgreSQL 16, and MySQL 8.4 tested with real databases. MySQL live coverage includes tenant routing, codecs, stable paging, limits, sort rejection, and command lookup. PostgreSQL command lookup with node-postgres covers tenant routing, typed keys, and missing rows. No migrations, change tracking, or transactions. See [how it is checked](#sql-checks). | `Source/Drizzle/for_DrizzleReadModelForCommandResolver`, `Source/Drizzle/for_DrizzleReadModels`, `.../for_ColumnCodec`, `.../for_DrizzleModelCodec`, `.../for_withDrizzle`, `bash Source/Drizzle/run-integration.sh` (live PostgreSQL 16 and MySQL 8.4, Docker) |
| [SQL table observation](../sql/observing-tables.md) | Experimental | `DrizzleObservation.InProcess` observes only explicitly announced changes in the same process and tenant. Pages converge after announced writes but are not atomic snapshots; host-owned outer transactions require notification after commit. No PostgreSQL LISTEN/NOTIFY or polling. | `Source/Drizzle/for_DrizzleReadModels/when_observing`, `.../when_serving_a_sqlite_observation`, `.../for_DrizzleHandle` |
| [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. Full .NET parity is unverified. See [how it is checked](#chronicle-checks). | `Source/Chronicle/for_ChronicleResponseHandler`, `.../for_ChronicleUnitOfWork`, `.../for_ChronicleReadModelForCommandResolver`, `.../for_reactorCommandResultHandler`, `.../for_AggregateRoot`, `Source/Chronicle/testing/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. Arc releases protected models decoded into the exact read-model class at the query edge; failed release fails the result. | `Source/Chronicle/for_ChronicleReadModelInterceptor`, `Source/Chronicle/for_ChronicleReadModelForCommandResolver/when_resolving_a_private_projection`, `Source/Chronicle/Integration/live.test.mjs` |
| Reactor replay exclusion | Bounded (SDK 6.9.0+) | The SDK replays reactors by default and supports class- or handler-level `@onceOnly()` and alternate `@replay()` handlers. Arc executes returned commands under those SDK rules; type-checked `ARCCHR0006` warns on returned commands without a replay decision. Failed-partition recovery can re-deliver effects even with `@onceOnly()`. See [Reactors](../chronicle/reactors/index.md). | SDK decorators; `Source/CodeAnalysis/for_rules/when_linting_artifacts/with_reactor_replay_decisions.ts` checks the lint rule; `Source/Chronicle/Integration/LiveArtifacts.ts` and `bash Source/Chronicle/run-integration.sh` exercise a once-only reactor returning a command on normal delivery, not replay exclusion |
| [Chronicle code analysis](../chronicle/code-analysis.md) | Bounded | `ARCCHR0003` checks reactor store fields initialized from `this.client`/`this.runtime` (ownership is not proven); type-checked `ARCCHR0006` flags returned commands from live handlers without a replay decision; `ARCCHR0007` flags direct default-log appends from command `handle`/`provide`, including injected Chronicle services; `ARCCHR0009` checks unmasked secret-looking fields; type-checked `ARCCHR0010` flags Guid values beside direct decorated events on keyless commands. Other .NET diagnostics are inapplicable, checked at runtime, or require review. | `Source/CodeAnalysis/for_rules/when_linting_artifacts/with_chronicle_rules.ts`, `Source/CodeAnalysis/for_rules/when_linting_artifacts/with_reactor_replay_decisions.ts` |
| Transactions and units of work | Experimental | Chronicle stages returned and aggregate-applied events from nested commands in one tenant, correlation, and event store, and sends one `appendMany` after the outer command succeeds. It participates in Arc command-operation failure handling; it is not a transaction across stores or immediate SDK appends. | `Source/Chronicle/for_ChronicleUnitOfWork`, `Source/Chronicle/for_ChronicleCommandScope` |

- **MongoDB.** Also [joined observation](../mongodb/joined-observe.md), a [scoped watcher](../mongodb/change-stream-watcher.md), [GeoJSON geometry](../mongodb/geospatial.md), bounded transient read retries, MongoDB driver metrics for Arc-owned clients, and `Cratis:MongoDB:{Server,Database}` configuration binding. No 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.
- **SQL with Drizzle.** Command read models load by a single column-level `.primaryKey()` with `@field` in the tenant scope; models without `@field` on their column-level key still serve queries but cannot be injected into commands. Tables without a column-level primary key are rejected at registration. Custom columns bind typed keys, plain columns primitives. Command read models are tested against SQLite, live MySQL 8.4, and live PostgreSQL 16 with node-postgres. PostgreSQL coverage includes typed GUID and concept keys, tenant isolation, and missing required and optional rows.
- **SQL with Drizzle.** `bash Source/Drizzle/run-integration.sh` exercises live PostgreSQL and MySQL for existing SQL reads and command lookup, not observation. In-process observation is checked using SQLite (`sql.js`), gated race and burst specs, and SSE/GET through Express, Fastify, and Hono. Command read models load by a single column-level `.primaryKey()` with `@field` in the tenant scope; models without `@field` on their column-level key still serve queries but cannot be injected into commands. Tables without a column-level primary key are rejected at registration. Custom columns bind typed keys, plain columns primitives. Command read models are tested against SQLite, live MySQL 8.4, and live PostgreSQL 16 with node-postgres. PostgreSQL coverage includes typed GUID and concept keys, tenant isolation, and missing required and optional rows.
- **Chronicle.** It also resolves Chronicle read models by command key and in validators, batches nested returned events, and executes Arc commands returned from reactors through the SDK reactor result hook. SDK 6.14.0 loads in native Node ESM. Keyed aggregates and returned reactor commands are experimental.
- **Chronicle compliance.** Mark projected read-model properties `@pii()` to encrypt them at rest; event-only marking does not protect the materialized field. Chronicle kernel reads already release values (including command injection), and Arc skips releasing those instances twice. For protected Chronicle models decoded into the exact read-model class by `MongoCollection`, Arc releases at the query edge, including snapshots, pages, and observable emissions. `MongoReadModels` returns raw driver documents; these, other raw documents, codec-selected derived subtypes, and DTOs/mapped objects are served as stored and need explicit `readModels.release` on the tenant store. Nested, array-item, class-level PII and `@encrypted()` security metadata are detected. A directly read protected model needs `@subject()` or `id` matching the event subject to release. The kernel integration checks raw MongoDB ciphertext, Chronicle delivery, and Arc query-edge release for a direct MongoDB read.

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.37.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.38.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:

- **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 <package> pack --out <file>` 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.
Expand Down
4 changes: 3 additions & 1 deletion Documentation/sql/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,12 @@ export class AddTask {
@inject(drizzleDatabase<SQLJsDatabase>())
handle(database: DrizzleHandle<SQLJsDatabase>): void {
database.native.insert(tasks).values({ id: this.id, title: this.title }).run();
database.notifyChanged(tasks);
}
}
```

`drizzleDatabase<T>()` resolves to a `DrizzleHandle` whose `native` property is the tenant's Drizzle database, typed as you declare it. Register `AddTask` with `builder.add(...)` like the query.
`drizzleDatabase<T>()` resolves to a `DrizzleHandle` whose `native` property is the tenant's Drizzle database, typed as you declare it. Register `AddTask` with `builder.add(...)` like the query. `notifyChanged(tasks)` validates the registered table; it publishes changes only when you opt in to [in-process observation](observing-tables.md). Inside the command, publication follows execution (including a transaction awaited inside it), not a transaction owned by a host or outer runner. For those, call it after commit.

## Load a read model in a command

Expand Down Expand Up @@ -146,6 +147,7 @@ Queries take `drizzleReadModel(Model)`, commands take `drizzleDatabase()`. The r
| `findOne(filter)` | The first match in primary-key order, or `undefined` |
| `findById(key)` | The row matching the single primary key, or `null` |
| `table` | The Drizzle table |
| `observe(filter?)`, `observePage(filter, options)`, `observeById(key)` | Experimental opt-in in-process SQL observations; [limits](observing-tables.md) |

It has no write methods and does not expose the writable database. A filter is a Drizzle `SQL` expression such as `eq(tasks.title, 'a')`, built with bound parameters; never interpolate request input into SQL text.

Expand Down
3 changes: 2 additions & 1 deletion Documentation/sql/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Your read models live in SQL tables. Every query needs the right database for th
| Store GUIDs, concepts, dates, times, durations, and JSON per dialect | [Column types](column-types.md) |
| Map model fields to columns, generate and apply migrations, and choose database credentials | [Map the schema and own migrations](schema-and-migrations.md) |
| Count, sort, and page in SQL | [Paging and sorting](paging.md) |
| Announce writes and observe tenant-scoped SQL results in process (Experimental) | [Observe tables](observing-tables.md) |
| Route each tenant to its own database | [Tenancy](tenancy.md) |

## Why Drizzle
Expand All @@ -28,7 +29,7 @@ Drizzle 0.45 is before 1.0. The peer dependency `^0.45.0` accepts 0.45 releases,
## What it does not do

- **No schema management.** `withDrizzle` never creates tables, adds columns, or runs migrations. See [Own the schema](getting-started.md#own-the-schema).
- **No live queries.** There is no `observe()`, and Arc does not refresh an observable query when a table changes. SQLite has no cross-process change notification here, and Drizzle does not announce writes. PostgreSQL `LISTEN`/`NOTIFY` would need managed triggers, a listener connection per tenant, resubscription after reconnects, a race-free first read, and tested shutdown; none of that is included. If your application has a reliable, tenant-scoped change source of its own, an Arc [observable query](../queries/observable-queries.md) can consume it. An in-process event after a command write does not see changes made by other processes.
- **Only announced in-process changes.** Opt-in `DrizzleObservation.InProcess` observes registered tables after explicit `notifyChanged` calls. Other processes and unannounced writes are invisible; host-owned outer transactions are not covered by the command completion boundary. Observed pages are eventually consistent, not atomic count-and-row snapshots. PostgreSQL `LISTEN`/`NOTIFY` is not included. See [Observe tables](observing-tables.md).
- **No transactions or change tracking.** There is no unit of work shared with command execution. Use a Drizzle transaction in your command when several writes must succeed together.
- **One registration per application.** A second `withDrizzle` fails at build with a duplicate service. Several tenants use one registration with `databaseFactory`.
- **No EF-only features.** Arc on .NET's Entity Framework integration also has SQL Server, spatial Point, LineString, and Polygon types, several DbContexts, and automatic concept conversion in `BaseDbContext`. None of those are part of this package.
Expand Down
Loading
Loading