@cratis/pi 7.20.0 → 7.21.0
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.
|
@@ -64,15 +64,23 @@ public record AuthorName(string Value) : ConceptAs<string>(Value)
|
|
|
64
64
|
## Chronicle stream identity rules
|
|
65
65
|
|
|
66
66
|
```csharp
|
|
67
|
-
public record AuthorId(Guid Value) : EventSourceId<Guid>(Value)
|
|
67
|
+
public record AuthorId(Guid Value) : EventSourceId<Guid>(Value), IGeneratable<AuthorId>
|
|
68
68
|
{
|
|
69
|
-
public static AuthorId New() => new(
|
|
69
|
+
public static AuthorId New() => new(GenerateValue.Uuid());
|
|
70
70
|
public static implicit operator AuthorId(Guid value) => new(value);
|
|
71
71
|
}
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
- `New()`, primitive → derived ID conversion, and sentinels are optional domain
|
|
75
75
|
conveniences; `EventSourceId<T>` does not create an arbitrary derived type.
|
|
76
|
+
- When the system creates the value, implement `IGeneratable<TSelf>` and use
|
|
77
|
+
`GenerateValue.Uuid()` (or `GenerateValue.UuidV7()` when index locality
|
|
78
|
+
matters), both from `Cratis.Concepts` in Cratis.Fundamentals 7.23.0 or later.
|
|
79
|
+
Never `System.Random`. A client-supplied identifier is an input, not something
|
|
80
|
+
to generate; see the **cratis-fundamentals-concept** skill.
|
|
81
|
+
- Arc and Chronicle can resolve an older Cratis.Fundamentals. Check the resolved
|
|
82
|
+
version first; if it is below 7.23.0, add a direct `Cratis.Fundamentals`
|
|
83
|
+
7.23.0 or later reference, or keep the existing `static New()` (without the interface) until you can.
|
|
76
84
|
- The exact `EventSourceId<T>` base supports conversions among its underlying
|
|
77
85
|
`T`, string, typed ID, and untyped `EventSourceId`, but those operators do not
|
|
78
86
|
construct your derived domain record from every source form.
|
|
@@ -54,10 +54,10 @@ Every domain identifier or named value is a typed record — raw `string`/`Guid`
|
|
|
54
54
|
- **[contract] Chronicle stream identities** derive from `EventSourceId<T>` with an `IComparable` underlying primitive. Add `NotSet`, `New()`, or a `T` → derived-id operator only when the domain API justifies it; typed empty/zero values are real specified stream IDs. The base conversion surface does not construct every derived domain record automatically.
|
|
55
55
|
|
|
56
56
|
```csharp
|
|
57
|
-
public record AuthorId(Guid Value) : EventSourceId<Guid>(Value)
|
|
57
|
+
public record AuthorId(Guid Value) : EventSourceId<Guid>(Value), IGeneratable<AuthorId>
|
|
58
58
|
{
|
|
59
59
|
public static readonly AuthorId NotSet = new(Guid.Empty);
|
|
60
|
-
public static AuthorId New() => new(
|
|
60
|
+
public static AuthorId New() => new(GenerateValue.Uuid());
|
|
61
61
|
public static implicit operator AuthorId(Guid value) => new(value);
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -67,6 +67,8 @@ public record AuthorName(string Value) : ConceptAs<string>(Value)
|
|
|
67
67
|
}
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
`IGeneratable<T>` and `GenerateValue` need Cratis.Fundamentals 7.23.0 or later. Arc and Chronicle can resolve an older version; add a direct reference or keep a plain `static New()` until then (see [concepts.md](./concepts.md)).
|
|
71
|
+
|
|
70
72
|
**[convention] Placement:** slice-specific concept → in the slice file; feature-shared → feature folder; module-shared → module folder; app-wide → `Common/`. Scan `Common/` and the feature folder before creating a new concept. See [concepts.md](./concepts.md) for full patterns, or invoke the **cratis-fundamentals-concept** skill.
|
|
71
73
|
|
|
72
74
|
## Commands
|
|
@@ -15,7 +15,7 @@ This skill is verified against these exact public releases:
|
|
|
15
15
|
|
|
16
16
|
| Package | Version | Purpose |
|
|
17
17
|
| --- | --- | --- |
|
|
18
|
-
| `Cratis.Fundamentals` | `7.
|
|
18
|
+
| `Cratis.Fundamentals` | `7.23.0` | `Cratis.Concepts.ConceptAs<T>`, `IGeneratable<TSelf>` and `GenerateValue` |
|
|
19
19
|
| `Cratis.Chronicle` | `18.3.0` | `Cratis.Chronicle.Events.EventSourceId` and `EventSourceId<T>` |
|
|
20
20
|
|
|
21
21
|
Reverify product sources before claiming support for another version.
|
|
@@ -76,6 +76,55 @@ A `NotSet` or `Empty` value is optional domain policy. Add one only when the
|
|
|
76
76
|
chosen primitive value is impossible or explicitly reserved in that domain.
|
|
77
77
|
Do not assume `string.Empty`, `0`, or `Guid.Empty` is universally invalid.
|
|
78
78
|
|
|
79
|
+
## Let a concept generate its own value
|
|
80
|
+
|
|
81
|
+
Check the Cratis.Fundamentals version the project resolves first. Arc and
|
|
82
|
+
Chronicle can bring an older one; when it is below 7.23.0, add a direct
|
|
83
|
+
`Cratis.Fundamentals` 7.23.0 or later package reference, or keep the existing
|
|
84
|
+
`static New()` (without the interface) until you can.
|
|
85
|
+
|
|
86
|
+
When the system, not the caller, creates a value, the concept declares how by
|
|
87
|
+
implementing `IGeneratable<TSelf>` (`Cratis.Concepts`, Cratis.Fundamentals
|
|
88
|
+
7.23.0 or later). The interface requires `static abstract TSelf New()`, so
|
|
89
|
+
generic code constrained on `where T : IGeneratable<T>` calls `T.New()` without
|
|
90
|
+
reflection. Generate a UUID with `GenerateValue`, never with `Guid.NewGuid()` in
|
|
91
|
+
scattered call sites and never with `System.Random`:
|
|
92
|
+
|
|
93
|
+
```csharp
|
|
94
|
+
using Cratis.Concepts;
|
|
95
|
+
|
|
96
|
+
public record <ConceptName>(Guid Value) : ConceptAs<Guid>(Value), IGeneratable<<ConceptName>>
|
|
97
|
+
{
|
|
98
|
+
public static <ConceptName> New() => new(GenerateValue.Uuid());
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Both helpers return a `Guid` with the RFC 9562 variant and fill their random
|
|
103
|
+
bits from `RandomNumberGenerator`:
|
|
104
|
+
|
|
105
|
+
- `GenerateValue.Uuid()` — version 4, 122 random bits. The default.
|
|
106
|
+
- `GenerateValue.UuidV7()` — version 7: Unix-epoch milliseconds first, then 74
|
|
107
|
+
random bits. Choose it only when database index locality or rough creation
|
|
108
|
+
order matters, after checking how the database stores and compares UUIDs. It reveals the creation time, it is **not** monotonic within a
|
|
109
|
+
millisecond, and clock adjustments can reorder values.
|
|
110
|
+
|
|
111
|
+
Neither helper guarantees uniqueness; a collision is extremely unlikely, not
|
|
112
|
+
impossible. `IGeneratable<TSelf>` is not limited to Guid-backed concepts, but
|
|
113
|
+
`GenerateValue` only produces UUIDs.
|
|
114
|
+
|
|
115
|
+
Do not generate when:
|
|
116
|
+
|
|
117
|
+
- The client supplies the identifier. It is an input; keep it.
|
|
118
|
+
- The value must be secret or authenticate someone. A UUID is an identifier,
|
|
119
|
+
not a token; use a dedicated secure-token API.
|
|
120
|
+
- The same input must yield the same value. Random generation is not
|
|
121
|
+
idempotency; derive the value deterministically, or persist and reuse the
|
|
122
|
+
first one.
|
|
123
|
+
|
|
124
|
+
In specifications, construct the concept from a known value
|
|
125
|
+
(`new <ConceptName>(Guid.Parse("…"))`) and assert against it. Do not seed or
|
|
126
|
+
replace the generator. Guide: `/fundamentals/csharp/generating-values/`.
|
|
127
|
+
|
|
79
128
|
## Create a Guid-backed Chronicle stream identity
|
|
80
129
|
|
|
81
130
|
Use this shape only for an identity actually supplied to Chronicle append/read
|
|
@@ -86,6 +135,7 @@ operations as the event-source ID.
|
|
|
86
135
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
87
136
|
|
|
88
137
|
using Cratis.Chronicle.Events;
|
|
138
|
+
using Cratis.Concepts;
|
|
89
139
|
|
|
90
140
|
namespace <NamespaceRoot>.<Feature>;
|
|
91
141
|
|
|
@@ -93,13 +143,13 @@ namespace <NamespaceRoot>.<Feature>;
|
|
|
93
143
|
/// Represents the event-source identity of a <description>.
|
|
94
144
|
/// </summary>
|
|
95
145
|
/// <param name="Value">The underlying Guid value.</param>
|
|
96
|
-
public record <ConceptName>(Guid Value) : EventSourceId<Guid>(Value)
|
|
146
|
+
public record <ConceptName>(Guid Value) : EventSourceId<Guid>(Value), IGeneratable<<ConceptName>>
|
|
97
147
|
{
|
|
98
148
|
/// <summary>
|
|
99
149
|
/// Creates a new <ConceptName>.
|
|
100
150
|
/// </summary>
|
|
101
151
|
/// <returns>A new <ConceptName>.</returns>
|
|
102
|
-
public static <ConceptName> New() => new(
|
|
152
|
+
public static <ConceptName> New() => new(GenerateValue.Uuid());
|
|
103
153
|
|
|
104
154
|
/// <summary>
|
|
105
155
|
/// Converts a Guid to a <ConceptName>.
|
|
@@ -108,8 +158,8 @@ public record <ConceptName>(Guid Value) : EventSourceId<Guid>(Value)
|
|
|
108
158
|
}
|
|
109
159
|
```
|
|
110
160
|
|
|
111
|
-
`New()` and the primitive-to-derived conversion
|
|
112
|
-
type. `EventSourceId<T>` does not construct an arbitrary derived identity for
|
|
161
|
+
`New()` (through `IGeneratable<TSelf>`) and the primitive-to-derived conversion
|
|
162
|
+
are conveniences on this domain type. `EventSourceId<T>` does not construct an arbitrary derived identity for
|
|
113
163
|
you.
|
|
114
164
|
|
|
115
165
|
## Create a non-Guid Chronicle stream identity
|
|
@@ -185,6 +235,9 @@ repository structure.
|
|
|
185
235
|
- `ConceptAs<T>` and `EventSourceId<T>` use an `IComparable` underlying type.
|
|
186
236
|
- A concept contains exactly one wrapped value and no extra properties.
|
|
187
237
|
- Enums remain enums.
|
|
238
|
+
- A concept that creates its own value implements `IGeneratable<TSelf>`; a
|
|
239
|
+
Guid-backed one uses `GenerateValue`, not `Guid.NewGuid()` or `Random`.
|
|
240
|
+
Client-supplied, secret or idempotent values are not generated.
|
|
188
241
|
- Null absence uses a nullable concept reference rather than a null wrapped
|
|
189
242
|
value.
|
|
190
243
|
- Primitive-to-derived conversions and sentinels exist only when justified by
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
"input": "Create a strongly typed value for a domain concept.",
|
|
4
4
|
"fixtures": [],
|
|
5
5
|
"assertions": [
|
|
6
|
-
{ "kind": "skill-contains", "value": "ConceptAs<T>" }
|
|
6
|
+
{ "kind": "skill-contains", "value": "ConceptAs<T>" },
|
|
7
|
+
{ "kind": "skill-contains", "value": "IGeneratable<TSelf>" },
|
|
8
|
+
{ "kind": "skill-contains", "value": "GenerateValue.Uuid()" },
|
|
9
|
+
{ "kind": "skill-contains", "value": "not** monotonic within a" }
|
|
7
10
|
]
|
|
8
11
|
}
|