@cratis/pi 7.2.2 → 7.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package/corpus/agents/slice-implementer.md +1 -1
- package/package/corpus/prompts/add-business-rule.prompt.md +1 -1
- package/package/corpus/prompts/add-concept.prompt.md +1 -1
- package/package/corpus/prompts/add-ef-migration.prompt.md +1 -1
- package/package/corpus/prompts/add-projection.prompt.md +1 -1
- package/package/corpus/prompts/add-reactor.prompt.md +1 -1
- package/package/corpus/prompts/add-reducer.prompt.md +1 -1
- package/package/corpus/prompts/new-vertical-slice.prompt.md +2 -2
- package/package/corpus/prompts/scaffold-feature.prompt.md +2 -2
- package/package/corpus/prompts/write-specs.prompt.md +1 -1
- package/package/corpus/rules/application-profile.md +1 -1
- package/package/corpus/rules/framework.md +1 -1
- package/package/corpus/rules/frontend-testing.md +1 -1
- package/package/corpus/rules/profiles.md +23 -0
- package/package/corpus/rules/specs.csharp.md +2 -2
- package/package/corpus/rules/specs.scenarios.csharp.md +1 -1
- package/package/corpus/rules/vertical-slices.md +9 -9
- package/package/corpus/skills/cratis-arc-command-typescript/LICENSE +2 -0
- package/package/corpus/skills/cratis-arc-command-typescript/SKILL.md +495 -0
- package/package/corpus/skills/cratis-arc-command-typescript/references/chronicle-events.md +141 -0
- package/package/corpus/skills/cratis-arc-command-typescript/references/hosting-and-storage.md +183 -0
- package/package/corpus/skills/cratis-arc-command-typescript/verification.json +15 -0
- package/package/corpus/skills/cratis-arc-query-typescript/LICENSE +2 -0
- package/package/corpus/skills/cratis-arc-query-typescript/SKILL.md +350 -0
- package/package/corpus/skills/cratis-arc-query-typescript/verification.json +14 -0
- package/package/corpus/skills/cratis-arc-validation-typescript/LICENSE +2 -0
- package/package/corpus/skills/cratis-arc-validation-typescript/SKILL.md +320 -0
- package/package/corpus/skills/cratis-arc-validation-typescript/verification.json +14 -0
- package/package/profile-catalog.json +24 -0
- package/package.json +1 -1
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cratis-arc-validation-typescript
|
|
3
|
+
description: Add a rejection rule to a Cratis Arc command or query in a Node.js TypeScript server with Arc for TypeScript — choosing between @field shape, authorization, ConceptValidator, CommandValidator, QueryValidator with an arguments model, ModelValidator, readModelForValidation, and rejected()/denied() from provide() or handle(); the ruleFor vocabulary, which rules are copied into generated proxies, the ValidationResult helpers and validation(), severity filtering and the X-Allowed-Severity cap, and asserting rule failures in @cratis/arc.testing specs. Use when an Arc for TypeScript command or query must refuse work under some condition. Do not use to define a new command or query, for the .NET or Kotlin/Java validation shapes, or for append-time Chronicle constraints and concurrency.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Validate an Arc command or query in TypeScript (Node.js server)
|
|
8
|
+
|
|
9
|
+
A rule in Arc for TypeScript lives where the information it needs is available,
|
|
10
|
+
and as early as that allows. Validators run before `handle()` and also answer on
|
|
11
|
+
the command's `POST <route>/validate`, so a form can ask before the user
|
|
12
|
+
submits. Every rejection comes back as a `ValidationResult` with a message and
|
|
13
|
+
the member it concerns — never as a thrown error.
|
|
14
|
+
|
|
15
|
+
Arc for TypeScript is a **source preview**: `@cratis/arc.core` and the other
|
|
16
|
+
server packages are not published to npm, and the API may change. The published
|
|
17
|
+
`@cratis/arc` client is separate; it runs the client-safe rules the proxy
|
|
18
|
+
generator copies into generated proxies. Setup is in
|
|
19
|
+
`cratis-arc-command-typescript`.
|
|
20
|
+
|
|
21
|
+
## Verified product sources
|
|
22
|
+
|
|
23
|
+
Verified against `Cratis/Arc.TypeScript` `main` at commit `94d398d` (tag
|
|
24
|
+
`v0.33.0` plus three commits), reading `Source/Core/validation/**` (including
|
|
25
|
+
`index.ts`, `ValidationResult.ts`, `Severity.ts`, `RuleBuilder.ts`),
|
|
26
|
+
`Source/Core/commands/Outcome.ts`, `Documentation/commands/command-validation.md`,
|
|
27
|
+
`validation-severity-filtering.md`, `command-outcomes.md`,
|
|
28
|
+
`Documentation/queries/validation.md`, `Documentation/concepts.md`,
|
|
29
|
+
`Documentation/proxy-generation/validation.md`, `Source/Testing`, and the
|
|
30
|
+
`Samples/Tasks` validators. Rechecked against `main` at `3cc6ab3` (v0.34.0) for the result helpers.
|
|
31
|
+
`ValidationResult` is both the exported interface and a value with the helpers
|
|
32
|
+
`ValidationResult.information/warning/error(message, options?)`; the positional
|
|
33
|
+
`validation(...)` function remains available.
|
|
34
|
+
|
|
35
|
+
## Choose where to reject
|
|
36
|
+
|
|
37
|
+
| The decision depends on | Put it in | Runs on `/validate` | Caller sees |
|
|
38
|
+
| --- | --- | --- | --- |
|
|
39
|
+
| The request's shape: types, required/optional fields | `@field` plus `@optional()`/`@nullable()`/`@defaultValue()` | Yes | 400 `malformedRequest`, no message or members |
|
|
40
|
+
| Who is calling | `@roles`, `@authorize`, a policy | Yes | 401 or 403 |
|
|
41
|
+
| One value, wherever it appears | `ConceptValidator` on the concept | Yes | 400 with your message and member |
|
|
42
|
+
| The command's own fields, or a service | `CommandValidator` | Yes | 400 with your message and member |
|
|
43
|
+
| Stored state for the command's key | `CommandValidator` rule calling `readModelForValidation` | Yes | 400 with your message and member |
|
|
44
|
+
| Data you load anyway to do the work | `provide()` returning `rejected(...)` or `denied(...)` | No | 400 or 403 |
|
|
45
|
+
| A decision only the handler can make | `handle()` returning `rejected(...)` or `denied(...)` | No | 400 or 403 |
|
|
46
|
+
|
|
47
|
+
- **Reject as early as the information allows.** Everything through the
|
|
48
|
+
`readModelForValidation` row answers on `/validate`.
|
|
49
|
+
- **Keep access control out of validators.** A trusted caller can lower the
|
|
50
|
+
blocking severity and let validation results through; nothing lowers
|
|
51
|
+
authorization or `denied(...)`.
|
|
52
|
+
- A stored-state check tells you what was true when it ran. A rule that must hold
|
|
53
|
+
under concurrency belongs at the storage boundary (for Chronicle, a constraint
|
|
54
|
+
or concurrency scope — `cratis-chronicle-event-constraints`).
|
|
55
|
+
|
|
56
|
+
## Command rules — `CommandValidator`
|
|
57
|
+
|
|
58
|
+
`Samples/Tasks/Features/Tasks/Registration/Registration.ts` keeps the validator
|
|
59
|
+
in the slice file with its command:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { command, CommandValidator, validator } from '@cratis/arc.core';
|
|
63
|
+
|
|
64
|
+
@validator(RegisterTask)
|
|
65
|
+
export class RegisterTaskValidator extends CommandValidator<RegisterTask> {
|
|
66
|
+
constructor() {
|
|
67
|
+
super();
|
|
68
|
+
this.ruleFor(command => command.title).notEmpty().withMessage('A title is required');
|
|
69
|
+
this.ruleFor(command => command.title).maxLength(100).withMessage('A title can have at most 100 characters');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- `@validator(Target)` supplies the runtime target that the erased generic
|
|
75
|
+
cannot. It registers nothing globally: `discover()` picks the exported class up
|
|
76
|
+
from the folder, or pass it to `builder.add(RegisterTask, RegisterTaskValidator)`
|
|
77
|
+
and to `CommandScenario.for(...)`. A validator that is never imported is never
|
|
78
|
+
registered.
|
|
79
|
+
- One validator per **exact** target class; a duplicate target fails at build.
|
|
80
|
+
- Rules on a concept member receive the unwrapped primitive — `TaskTitle` rules
|
|
81
|
+
accept a `string`.
|
|
82
|
+
- Every rule runs; a failure does not stop the others, so a form sees every
|
|
83
|
+
problem at once. Any result above the allowed severity blocks `handle()`.
|
|
84
|
+
|
|
85
|
+
## Value rules — `ConceptValidator`
|
|
86
|
+
|
|
87
|
+
A rule that belongs to the value goes on the concept, once.
|
|
88
|
+
`Samples/Tasks/Features/Tasks/TaskTitleValidator.ts`:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { ConceptValidator, validator } from '@cratis/arc.core';
|
|
92
|
+
import { TaskTitle } from './TaskTitle.js';
|
|
93
|
+
|
|
94
|
+
@validator(TaskTitle)
|
|
95
|
+
export class TaskTitleValidator extends ConceptValidator<TaskTitle> {
|
|
96
|
+
constructor() {
|
|
97
|
+
super();
|
|
98
|
+
this.ruleFor(title => title.value).must(value => !value.startsWith('!'))
|
|
99
|
+
.withMessage('A title cannot begin with an exclamation mark');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Arc walks the declared `@field` members of commands, query arguments and nested
|
|
105
|
+
models, and runs the concept validator on every `TaskTitle` it meets, including
|
|
106
|
+
inside arrays. A failure inside `entries[]` reports `entries.title`, not an
|
|
107
|
+
index. The owner's own rules still run. To skip only the **direct** member's
|
|
108
|
+
concept rules, call `.ignoreConceptRules()` on the owner's `ruleFor(...)` chain;
|
|
109
|
+
descendants are still traversed. The Library sample keeps `AuthorName` and its
|
|
110
|
+
`AuthorNameValidator` together in `AuthorName.ts`.
|
|
111
|
+
|
|
112
|
+
`ModelValidator<T>` validates a nested model class the same way, wherever it
|
|
113
|
+
appears in a command or query-argument graph.
|
|
114
|
+
|
|
115
|
+
## Query rules — `QueryValidator`
|
|
116
|
+
|
|
117
|
+
A `QueryValidator` targets an explicit **arguments model** whose `@field`
|
|
118
|
+
declarations match the query's `argument(...)` descriptors:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { ConceptAs, field } from '@cratis/fundamentals';
|
|
122
|
+
import { QueryValidator, argument, query, readModel, validator } from '@cratis/arc.core';
|
|
123
|
+
|
|
124
|
+
export class Name extends ConceptAs<string> { static readonly valueType = String; }
|
|
125
|
+
|
|
126
|
+
export class SearchArguments {
|
|
127
|
+
@field(Name) term!: Name;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@validator(SearchArguments)
|
|
131
|
+
export class SearchArgumentsValidator extends QueryValidator<SearchArguments> {
|
|
132
|
+
constructor() {
|
|
133
|
+
super();
|
|
134
|
+
this.ruleFor(arguments_ => arguments_.term).notEmpty().withMessage('Term required');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@readModel()
|
|
139
|
+
export class Search {
|
|
140
|
+
@query({ argumentsModel: SearchArguments }, argument('term', Name))
|
|
141
|
+
static byTerm(term: Name): string { return term.value; }
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Without an arguments model, Arc still runs concept validators on each supplied,
|
|
146
|
+
non-null argument under that argument's name, but a `QueryValidator` has nothing
|
|
147
|
+
to target. Queries always use `Warning` as the allowed severity.
|
|
148
|
+
|
|
149
|
+
## Rules that read stored state — `readModelForValidation`
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
import { field } from '@cratis/fundamentals';
|
|
153
|
+
import { command, CommandValidator, key, readModelForValidation, validator } from '@cratis/arc.core';
|
|
154
|
+
|
|
155
|
+
@command()
|
|
156
|
+
export class RenameTask {
|
|
157
|
+
@field(String) @key() id!: string;
|
|
158
|
+
@field(String) title!: string;
|
|
159
|
+
handle(): void { /* rename in storage */ }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@validator(RenameTask)
|
|
163
|
+
export class RenameTaskValidator extends CommandValidator<RenameTask> {
|
|
164
|
+
constructor() {
|
|
165
|
+
super();
|
|
166
|
+
this.ruleFor(command => command.title).mustAsync(async title => {
|
|
167
|
+
const current = await readModelForValidation(TaskView, { optional: true });
|
|
168
|
+
return current === null || current.title !== title;
|
|
169
|
+
}).withMessage('The task already has this title');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
- Arc resolves the command key (`@key()`, `getKey()`, or a resolver) and asks the
|
|
175
|
+
resolver that owns `TaskView` — registered by MongoDB, Drizzle, experimental
|
|
176
|
+
Chronicle, or `builder.addReadModelForCommandResolver(token)`. The resolver
|
|
177
|
+
receives the caller's tenant.
|
|
178
|
+
- `{ optional: true }` returns `null` for a missing model. Without it, a missing
|
|
179
|
+
model makes the rule throw, and the caller gets `validatorFailed` instead of
|
|
180
|
+
your message.
|
|
181
|
+
- It works **only** inside a validator of a model-bound command, during
|
|
182
|
+
validation. Validators do not receive read models through their constructors.
|
|
183
|
+
|
|
184
|
+
## Rule vocabulary
|
|
185
|
+
|
|
186
|
+
| Rules | Where they run |
|
|
187
|
+
| --- | --- |
|
|
188
|
+
| `notNull`, `notEmpty`, `minLength`, `maxLength`, `length`, `emailAddress`, `phone`, `url`, `matches`, `greaterThan`, `greaterThanOrEqual`, `lessThan`, `lessThanOrEqual` | Server; literal, unconditional uses are also copied into generated proxies |
|
|
189
|
+
| `empty`, `null`, `equal`, `notEqual`, `inclusiveBetween`, `exclusiveBetween`, `must`, `mustAsync` | Server only |
|
|
190
|
+
|
|
191
|
+
- `withMessage`, `withSeverity(Severity.Warning)`, and `withState(value)`
|
|
192
|
+
decorate the most recent rule.
|
|
193
|
+
- `when(predicate)` / `unless(predicate)` condition **every rule on the chain**
|
|
194
|
+
by default; pass `ApplyConditionTo.CurrentValidator` to condition only the
|
|
195
|
+
latest. Conditions and predicates run on the server only.
|
|
196
|
+
- `must` and `mustAsync` receive `(value, model, signal)`; pass the signal to
|
|
197
|
+
cancelable I/O.
|
|
198
|
+
- String rules exist only for strings; comparison rules only for numbers and
|
|
199
|
+
temporal values. `notEmpty()` rejects `Guid.empty`.
|
|
200
|
+
- Email, phone, URL, regex and Unicode-length behavior is not guaranteed to match
|
|
201
|
+
the client or Arc on .NET exactly. Write an explicit message and specify the
|
|
202
|
+
inputs you accept.
|
|
203
|
+
- The generator copies rules for commands (including direct concept fields) and
|
|
204
|
+
for query arguments with an explicit `argumentsModel`. A rule that stays
|
|
205
|
+
server-only produces a source diagnostic. The server validates every request
|
|
206
|
+
regardless.
|
|
207
|
+
|
|
208
|
+
## Services in a validator
|
|
209
|
+
|
|
210
|
+
Declare constructor dependencies with `@injectable(Service)` or
|
|
211
|
+
`static inject = [Service] as const`, and register the service with
|
|
212
|
+
`builder.services`. Arc constructs each validator once during `build()` (catching
|
|
213
|
+
invalid selectors early), then resolves fresh validators per execution scope. A
|
|
214
|
+
validator that throws, or whose dependency cannot be resolved, never reports
|
|
215
|
+
success: the caller gets 400 with reason `validatorFailed` or
|
|
216
|
+
`dependencyUnavailable`, no exception text, and the error goes to the logger.
|
|
217
|
+
|
|
218
|
+
## Reject from `provide()` or `handle()`
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import { denied, rejected, validation, Severity, ValidationResult } from '@cratis/arc.core';
|
|
222
|
+
|
|
223
|
+
return rejected(ValidationResult.error('The task does not exist', { members: ['id'], reason: 'notFound' }));
|
|
224
|
+
return rejected(ValidationResult.warning('Unusually long title', { members: ['title'], reasonDetail: 'TitleLength' }));
|
|
225
|
+
return rejected(validation('Unusually long title', ['title'], 'rule', Severity.Warning));
|
|
226
|
+
return denied('Only the owner can rename a task');
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
- `ValidationResult.information/warning/error(message, options?)` (Arc for
|
|
230
|
+
TypeScript 0.34.0 and later) build a result at that severity; `options` sets
|
|
231
|
+
`members`, `state` (must be JSON-serializable), `reason` (default `rule`) and
|
|
232
|
+
`reasonDetail`. `members` defaults to `[]` and `reason` to `rule`; omitted
|
|
233
|
+
`state` and `reasonDetail` are left out of the result.
|
|
234
|
+
- `validation(message, members = [], reason = 'rule', severity = Severity.Error)`
|
|
235
|
+
builds a `ValidationResult` (`{ severity, message, members, reason }`) with
|
|
236
|
+
positional arguments; it cannot set `reasonDetail` or `state`.
|
|
237
|
+
- `rejected(...results)` answers 400; it throws when given no results, so an
|
|
238
|
+
empty rejection can never pass as success. `denied(reason?)` answers 403 with
|
|
239
|
+
`authorizationFailureReason`.
|
|
240
|
+
- Rejected results go through the same severity filter. When nothing above the
|
|
241
|
+
allowed severity remains, `handle()` runs (from `provide()`, with `undefined`
|
|
242
|
+
as the provided value) or the command succeeds without a response (from
|
|
243
|
+
`handle()`).
|
|
244
|
+
- Throwing is for failures, not decisions: a thrown error is a 500.
|
|
245
|
+
|
|
246
|
+
## Severity filtering
|
|
247
|
+
|
|
248
|
+
`Severity` is `Unknown` (0), `Information` (1), `Warning` (2), `Error` (3).
|
|
249
|
+
Results at or below the **allowed severity** are removed and do not block;
|
|
250
|
+
results above it block and are returned. The default is `Warning`, so only
|
|
251
|
+
errors block.
|
|
252
|
+
|
|
253
|
+
| Caller | Allowed severity |
|
|
254
|
+
| --- | --- |
|
|
255
|
+
| HTTP command | `X-Allowed-Severity` header `0`, `1` or `2`; missing or invalid means `2`; `3` is **capped** to `2`, so errors always block |
|
|
256
|
+
| HTTP query | Always `Warning`; the header is ignored |
|
|
257
|
+
| `executeCommand` from code, or `CommandScenario.withAllowedValidationSeverity(...)` | As given, including `Severity.Error` |
|
|
258
|
+
| `performQuery` from code | Always `Warning` |
|
|
259
|
+
|
|
260
|
+
The HTTP cap at `Warning` is a deliberate safety difference from Arc on .NET,
|
|
261
|
+
which lets `X-Allowed-Severity: 3` run a command whose only problems are
|
|
262
|
+
errors. Do not "fix" it toward .NET behavior. `@command({ treatWarningsAsErrors: true })`
|
|
263
|
+
and `@query({ treatWarningsAsErrors: true })` set the generated client's flag.
|
|
264
|
+
|
|
265
|
+
## Specify the rule
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import { given, type ScenarioCommandResult } from '@cratis/arc.testing';
|
|
269
|
+
|
|
270
|
+
describe('when validating a task with an empty title', given(a_task_registration, context => {
|
|
271
|
+
let result: ScenarioCommandResult;
|
|
272
|
+
beforeEach(async () => {
|
|
273
|
+
result = await context.scenario.validate({ id: TaskId.create(), title: new TaskTitle('') });
|
|
274
|
+
});
|
|
275
|
+
afterAll(async () => { await context.scenario.dispose(); });
|
|
276
|
+
it('should report the authored rule for title', () => {
|
|
277
|
+
result.shouldHaveValidationErrors().shouldHaveValidationErrorForMember('title');
|
|
278
|
+
});
|
|
279
|
+
it('should not invoke the handler', () => { context.tasks.all().should.have.lengthOf(0); });
|
|
280
|
+
}));
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
- Use `scenario.validate(...)` to prove the rule answers on `/validate`, and
|
|
284
|
+
`execute(...)` to prove `handle()` never ran.
|
|
285
|
+
- Assert the member with `shouldHaveValidationErrorForMember('title')`.
|
|
286
|
+
`shouldHaveValidationErrorFor(text)` passes for any message **containing** the
|
|
287
|
+
text; pass the full message when the wording matters.
|
|
288
|
+
- `validatorFailed` and `dependencyUnavailable` never satisfy
|
|
289
|
+
`shouldHaveValidationErrors()` or the member assertion — they mean no authored
|
|
290
|
+
rule was established. Assert them explicitly with
|
|
291
|
+
`shouldHaveValidationErrorBecauseOf('validatorFailed')`.
|
|
292
|
+
`shouldHaveRuleFailure(result, { reason, member?, severity? })` from
|
|
293
|
+
`@cratis/arc.testing` asserts an authored rule on a plain result.
|
|
294
|
+
- For a query, `QueryScenario.perform(...)` returns a result whose
|
|
295
|
+
`validationResults` you assert the same way.
|
|
296
|
+
|
|
297
|
+
## Route near misses
|
|
298
|
+
|
|
299
|
+
- Defining the command or query the rule attaches to:
|
|
300
|
+
`cratis-arc-command-typescript`, `cratis-arc-query-typescript`.
|
|
301
|
+
- Append-time uniqueness or concurrency in Chronicle:
|
|
302
|
+
`cratis-chronicle-event-constraints` and the Chronicle reference in
|
|
303
|
+
`cratis-arc-command-typescript`.
|
|
304
|
+
- The .NET validation shape: `cratis-arc-command-validation`; Kotlin/Java:
|
|
305
|
+
`cratis-arc-validation-kotlin`.
|
|
306
|
+
|
|
307
|
+
## Verify
|
|
308
|
+
|
|
309
|
+
- Each rule sits in the earliest row of the table that has the information it
|
|
310
|
+
needs; access control is in authorization or `denied(...)`, not a validator.
|
|
311
|
+
- Every validator is exported (or passed to `add()`), extends the right base
|
|
312
|
+
class, and carries `@validator(ExactTarget)`.
|
|
313
|
+
- Rejections are `ValidationResult`s built with `ValidationResult.error/warning/information(...)`,
|
|
314
|
+
`validation(...)` or rule messages — never thrown errors.
|
|
315
|
+
- `readModelForValidation` is called only inside a model-bound command's
|
|
316
|
+
validator, with `{ optional: true }` wherever absence is a legitimate state.
|
|
317
|
+
- Generated metadata and proxies are regenerated after a validator changes, so
|
|
318
|
+
the client-copied rules match the server's.
|
|
319
|
+
- Specs assert the authored rule and member through `validate()` and prove
|
|
320
|
+
`handle()` did not run.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill": "cratis-arc-validation-typescript",
|
|
3
|
+
"input": "Reject an Arc for TypeScript command when the title is empty or unchanged.",
|
|
4
|
+
"fixtures": [],
|
|
5
|
+
"assertions": [
|
|
6
|
+
{ "kind": "skill-contains", "value": "is a **source preview**" },
|
|
7
|
+
{ "kind": "skill-contains", "value": "`ValidationResult.information/warning/error(message, options?)`" },
|
|
8
|
+
{ "kind": "skill-contains", "value": "`validation(message, members = [], reason = 'rule', severity = Severity.Error)`" },
|
|
9
|
+
{ "kind": "skill-contains", "value": "**Keep access control out of validators.**" },
|
|
10
|
+
{ "kind": "skill-contains", "value": "`3` is **capped** to `2`, so errors always block" },
|
|
11
|
+
{ "kind": "skill-contains", "value": "It works **only** inside a validator of a model-bound command" },
|
|
12
|
+
{ "kind": "skill-contains", "value": "shouldHaveValidationErrorForMember('title')" }
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
"cratis/arc/java",
|
|
24
24
|
"cratis/arc/kotlin",
|
|
25
25
|
"cratis/arc/react",
|
|
26
|
+
"cratis/arc/server-typescript",
|
|
27
|
+
"cratis/arc/typescript",
|
|
26
28
|
"cratis/chronicle",
|
|
27
29
|
"cratis/chronicle/client-dotnet",
|
|
28
30
|
"cratis/chronicle/client-elixir",
|
|
@@ -335,6 +337,27 @@
|
|
|
335
337
|
"typescript"
|
|
336
338
|
]
|
|
337
339
|
},
|
|
340
|
+
{
|
|
341
|
+
"id": "cratis/arc/server-typescript",
|
|
342
|
+
"availableTargets": [
|
|
343
|
+
"cratis-arc-command-typescript",
|
|
344
|
+
"cratis-arc-query-typescript",
|
|
345
|
+
"cratis-arc-validation-typescript"
|
|
346
|
+
],
|
|
347
|
+
"languages": [
|
|
348
|
+
"typescript"
|
|
349
|
+
]
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
"id": "cratis/arc/typescript",
|
|
353
|
+
"composes": [
|
|
354
|
+
"cratis/arc/server-typescript",
|
|
355
|
+
"cratis/language/typescript"
|
|
356
|
+
],
|
|
357
|
+
"languages": [
|
|
358
|
+
"typescript"
|
|
359
|
+
]
|
|
360
|
+
},
|
|
338
361
|
{
|
|
339
362
|
"id": "cratis/chronicle",
|
|
340
363
|
"composes": [
|
|
@@ -666,6 +689,7 @@
|
|
|
666
689
|
"id": "cratis/full/typescript",
|
|
667
690
|
"composes": [
|
|
668
691
|
"cratis/application/typescript",
|
|
692
|
+
"cratis/arc/typescript",
|
|
669
693
|
"cratis/chronicle/typescript",
|
|
670
694
|
"cratis/modeling/screenplay-stage"
|
|
671
695
|
],
|