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.35.0",
"version": "0.36.0",
"private": true,
"type": "module",
"dependencies": {
Expand Down
19 changes: 11 additions & 8 deletions Documentation/chronicle/code-analysis.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ description: Arc on .NET's Chronicle diagnostics mapped to TypeScript lint rules
---

Arc on .NET v22.23.0 ships ten Chronicle analyzers (`ARCCHR0001`–`ARCCHR0010`).
Four have bounded TypeScript ESLint analogs in `@cratis/eslint-plugin-arc-core`.
Five have bounded TypeScript ESLint analogs in `@cratis/eslint-plugin-arc-core`.
Configure the plugin as described in [Code analysis](../code-analysis/index.md).
Both presets enable `arcchr0003`, `arcchr0007`, and `arcchr0009`.
`arcchr0010` needs type information and is enabled by `recommended-type-checked`.
`arcchr0006` and `arcchr0010` need type information and are enabled by `recommended-type-checked`.
ESLint reports enabled rules as errors, including analogs of .NET warnings.

## ARCCHR mapping
Expand All @@ -19,7 +19,7 @@ ESLint reports enabled rules as errors, including analogs of .NET warnings.
| [ARCCHR0003](../code-analysis/ARCCHR0003.md), reactor reaches default log | Warning | ESLint analog |
| ARCCHR0004, redundant `[EventType]` id | Warning | N/A |
| ARCCHR0005, Chronicle used but not configured | Warning | Partly caught at runtime |
| ARCCHR0006, reactor executes a command without replay decision | Warning | Not implemented yet |
| [ARCCHR0006](../code-analysis/ARCCHR0006.md), reactor returns a command without a replay decision | Warning | Type-checked ESLint analog |
| [ARCCHR0007](../code-analysis/ARCCHR0007.md), command injects event log | Warning | ESLint analog |
| ARCCHR0008, data annotations `[Key]` | Warning | N/A |
| [ARCCHR0009](../code-analysis/ARCCHR0009.md), secret-looking command property | Warning | ESLint analog for names not masked at runtime |
Expand All @@ -37,9 +37,11 @@ ESLint reports enabled rules as errors, including analogs of .NET warnings.
- **ARCCHR0005:** `commandReadModel(Type)` without an owner fails `build()`.
A returned event without `withChronicle` becomes an ordinary response and is not caught.
A per-file ESLint rule cannot prove registration in a separate host module.
- **ARCCHR0006:** Not implemented yet. TypeScript reactors normally return commands instead of calling
.NET's `ICommandPipeline.Execute`; a returned command still needs an explicit replay policy.
The SDK supports `@onceOnly()` and `@replay()`, so a bounded lint rule is feasible.
- **ARCCHR0006:** TypeScript reactors return Arc commands rather than calling .NET's
`ICommandPipeline.Execute`. The type-checked rule follows same-class helper calls back to live
handlers and warns when none has a replay decision. Class- or handler-level `@onceOnly()` and an
`@replay()` handler for the same event silence it. Dispatch matches the camel-cased event class
name, not the parameter annotation. See [the rule's bounds](../code-analysis/ARCCHR0006.md).
- **ARCCHR0007:** The rule finds direct `eventLog.append` or `appendMany` calls from a command's
`handle()` or `provide()` through an artifact store or a `@inject(ChronicleReadModels | ChronicleRuntime)`
parameter, including local and inline `getStore()` calls. Indirect appends remain a review concern.
Expand All @@ -59,8 +61,9 @@ this mapping does not add either rule.
## What to check in review

- Appends through helper methods or stores not held directly by a command or reactor can bypass the return-value pipeline.
- Reactors returning commands need a replay decision; `@onceOnly()` skips replay but does not prevent
failed-partition re-delivery. See [Returning commands from a reactor](reactors/command-side-effects.md#when-a-command-fails).
- Commands executed manually inside a reactor (for example through an Arc server) are not detected.
They still need a replay decision; `@onceOnly()` skips replay but does not prevent failed-partition
re-delivery. See [Returning commands from a reactor](reactors/command-side-effects.md#when-a-command-fails).
- An app that returns events must install `withChronicle`; check the host, not just the artifact file.
- A tuple carrying an ordinary string instead of `eventSourceIdResponse(id)` does not select an event source;
see [Resolving the event source ID](resolving-event-source-id.md#return-the-id-to-the-caller).
Expand Down
49 changes: 49 additions & 0 deletions Documentation/code-analysis/ARCCHR0006.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
title: ARCCHR0006 — Reactor returning commands needs a replay decision
description: Declare how a Chronicle reactor that returns an Arc command behaves during replay.
---

A reactor returning an Arc `@command()` instance (or an array of commands) runs that command again
when Chronicle replays the event. Decide whether replay should run the effect: mark the reactor or
individual handler `@onceOnly()` to skip it, or provide an alternate `@replay()` handler for the
same event. This is a type-checked analog of the .NET warning for `ICommandPipeline.Execute`;
ESLint reports it as an error in the `recommended-type-checked` preset.

```ts
import { onceOnly, reactor } from '@cratis/chronicle/reactors';

@reactor()
class StockKeeping {
@onceOnly()
bookReserved(event: BookReserved): DecreaseStock {
return new DecreaseStock(event.isbn);
}
}
```

`@onceOnly()` is a replay decision, not duplicate protection: failed-partition recovery can still
re-deliver an event and run the command again. Do not use once-only if replay needs to rebuild the
side effect. Instead use `@replay()` on a separate method: `replayBookReserved()` for an event class
named `BookReserved`, or `@replay(BookReserved)` on a differently named method. Chronicle chooses
handlers by the event class name, **not** by the TypeScript type annotation of the first parameter.
An alternate replay handler takes over for that event even if it returns nothing. See
[Returning commands from a reactor](../chronicle/reactors/command-side-effects.md#when-a-command-fails).

The rule recognizes `@reactor()`, `@onceOnly()`, and `@replay()` imported from
`@cratis/chronicle/reactors` or `@cratis/chronicle`, and return expressions whose checked type is
an Arc `@command()` class (including top-level promises and one level of arrays). It follows
returned `this.method()` calls inside the same reactor through helpers and reports at the return
site, naming undecided handlers that reach it. Reactor class declarations and decorated class
expressions are supported. A replay handler must name that event by convention or pass its
constructor to `@replay(Event)`; a replay handler for another event does not count.

This is bounded static analysis: a live handler must have the event class's camel-cased name **and**
a first parameter typed as that same decorated `@eventType()` class. The SDK dispatches by name
alone, so a handler with no parameter, an `any` parameter, or a parameter typed as another event
class is missed. Decorators on classes from compiled shared packages are absent from their `.d.ts`
files, so those event and command classes are also missed. The rule does not prove artifact
registration, follow calls through other objects or inherited helpers, infer commands behind erased
`unknown`/`any` return types, or find commands executed manually through an Arc server instead of
returned. Conditional returned helpers are treated as reachable; calls whose results are discarded
are not. It does not assess whether replay and retries are safe for a particular command.
[Chronicle code analysis](../chronicle/code-analysis.md) maps the remaining diagnostics.
4 changes: 2 additions & 2 deletions Documentation/code-analysis/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default tseslint.config(
);
```

Run `eslint src`. Both presets enable the mapped ARC diagnostics, three Chronicle rules, and TypeScript-only rules below, except the opt-in name rule. `recommended` works without type information: type-dependent comparisons are skipped, while syntax checks still run. `recommended-type-checked` enables `parserOptions.projectService` to check parameter and token types and adds `arcchr0010`; files without an applicable tsconfig must be excluded or added to the project service. Both presets require a TypeScript parser. Arc decorators must resolve to imports from `@cratis/arc.core`, and `@field` must come from `@cratis/fundamentals` (including aliases and namespace imports); custom wrappers are not recognized. Runtime and proxy-generator checks remain authoritative.
Run `eslint src`. Both presets enable the mapped ARC diagnostics, three Chronicle rules, and TypeScript-only rules below, except the opt-in name rule. `recommended` works without type information: type-dependent comparisons are skipped, while syntax checks still run. `recommended-type-checked` enables `parserOptions.projectService` to check parameter and token types and adds `arcchr0006` and `arcchr0010`; files without an applicable tsconfig must be excluded or added to the project service. Both presets require a TypeScript parser. Arc decorators must resolve to imports from `@cratis/arc.core`, and `@field` must come from `@cratis/fundamentals` (including aliases and namespace imports); custom wrappers are not recognized. Runtime and proxy-generator checks remain authoritative.

## ARC diagnostic mapping

Expand All @@ -42,6 +42,6 @@ The .NET IDs are retained only when the same mistake can happen in the TypeScrip
| [ARC0019](ARC0019.md) | Analog | `@allowAnonymous()` conflicts with `@authorize()` or `@roles()` on one declaration. |
| ARC0020–ARC0021 | N/A | ASP.NET Core attributes and authentication schemes do not exist in this Node host. |

Chronicle analogs: [ARCCHR0003](ARCCHR0003.md), [ARCCHR0007](ARCCHR0007.md), [ARCCHR0009](ARCCHR0009.md), and type-checked [ARCCHR0010](ARCCHR0010.md). See [Chronicle code analysis](../chronicle/code-analysis.md) for the complete .NET ARCCHR mapping, including diagnostics without TypeScript equivalents.
Chronicle analogs: [ARCCHR0003](ARCCHR0003.md), type-checked [ARCCHR0006](ARCCHR0006.md), [ARCCHR0007](ARCCHR0007.md), [ARCCHR0009](ARCCHR0009.md), and type-checked [ARCCHR0010](ARCCHR0010.md). See [Chronicle code analysis](../chronicle/code-analysis.md) for the complete .NET ARCCHR mapping, including diagnostics without TypeScript equivalents.

TypeScript-only rules: [missing-field](missing-field.md), [declared-field](declared-field.md), [inject-binding](inject-binding.md), [query-binding](query-binding.md), [query-argument-name](query-argument-name.md) (opt-in), [misplaced-decorator](misplaced-decorator.md), [unexported-artifact](unexported-artifact.md), and [validator-target](validator-target.md). No automatic fix is offered: .NET's `Task` unwrap fix changes async rejection semantics in JavaScript; its `nameof` fix has no TypeScript equivalent. Manual fixes are shown on each page.
10 changes: 10 additions & 0 deletions Documentation/code-analysis/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@
href: ARC0015.md
- name: ARC0019 Authorization conflict
href: ARC0019.md
- name: ARCCHR0003 Reactor default log
href: ARCCHR0003.md
- name: ARCCHR0006 Reactor replay decision
href: ARCCHR0006.md
- name: ARCCHR0007 Command default log
href: ARCCHR0007.md
- name: ARCCHR0009 Sensitive command fields
href: ARCCHR0009.md
- name: ARCCHR0010 Guid response
href: ARCCHR0010.md
- name: Missing field
href: missing-field.md
- name: Declared field
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.35.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.36.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
4 changes: 2 additions & 2 deletions Documentation/reference/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ Evidence paths are relative to the repository root. Spec folders follow `for_<Su
| [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) |
| [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; no Arc lint rule enforces the markers. Failed-partition recovery can re-deliver effects even with `@onceOnly()`. See [Reactors](../chronicle/reactors/index.md). | SDK decorators; `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); `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. `ARCCHR0006` is not implemented yet; other .NET diagnostics are inapplicable, checked at runtime, or require review. | `Source/CodeAnalysis/for_rules/when_linting_artifacts/with_chronicle_rules.ts` |
| 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.
Expand Down
3 changes: 2 additions & 1 deletion Documentation/reference/diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Arc reports a problem at the earliest point it can see it. A binding mistake sho

## In the editor: lint rules

`@cratis/eslint-plugin-arc-core` checks model-bound artifacts before you build. Enabled rules report as errors. Both presets include bounded Chronicle checks for reactor and command appends and unmasked secret-looking fields when Chronicle is installed; `recommended-type-checked` also includes the Guid response check. `query-argument-name` is opt-in. Rules that match a .NET analyzer keep its `ARC` or `ARCCHR` code. Setup is in [Code analysis](../code-analysis/index.md).
`@cratis/eslint-plugin-arc-core` checks model-bound artifacts before you build. Enabled rules report as errors. Both presets include bounded Chronicle checks for reactor and command appends and unmasked secret-looking fields when Chronicle is installed; `recommended-type-checked` also includes reactor replay decisions and the Guid response check. `query-argument-name` is opt-in. Rules that match a .NET analyzer keep its `ARC` or `ARCCHR` code. Setup is in [Code analysis](../code-analysis/index.md).

| Rule | Reports |
| --- | --- |
Expand All @@ -21,6 +21,7 @@ Arc reports a problem at the earliest point it can see it. A binding mistake sho
| [`arc0015`](../code-analysis/ARC0015.md) | An incoming parameter converted to a concept inside the query |
| [`arc0019`](../code-analysis/ARC0019.md) | `@allowAnonymous()` combined with `@authorize()` or `@roles()` |
| [`arcchr0003`](../code-analysis/ARCCHR0003.md) | Direct reactor appends to its own default event log |
| [`arcchr0006`](../code-analysis/ARCCHR0006.md) | Returned Arc commands from live reactor handlers without a replay decision (type-checked preset) |
| [`arcchr0007`](../code-analysis/ARCCHR0007.md) | Direct default-log appends from a command's `handle()` or `provide()`, including injected Chronicle services and `.transactional` appends |
| [`arcchr0009`](../code-analysis/ARCCHR0009.md) | Unmasked secret-looking command fields and constructor parameter properties when Chronicle resolves |
| [`arcchr0010`](../code-analysis/ARCCHR0010.md) | A keyless command returning a type-checked Fundamentals `Guid` value beside a direct decorated event (type-checked preset) |
Expand Down
Loading
Loading