@reventlessdev/reventless-spec 3.0.0-alpha.139 → 3.0.0-alpha.140
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/CHANGELOG.md +16 -0
- package/package.json +1 -1
- package/src/components/IdentityCheck.res +110 -0
- package/src/components/IdentityCheck.res.mjs +103 -0
- package/src/components/StateChangeSlice.res +3 -5
- package/src/components/StateViewSlice.res +9 -1
- package/src/semantic/Semantic.res +11 -0
- package/src/semantic/Semantic.res.mjs +21 -0
- package/src/types/Projection.res +48 -4
- package/src/types/Projection.res.mjs +121 -8
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.140 (2026-09-21)
|
|
7
|
+
|
|
8
|
+
* feat(spec)!: typed projection keys ([fc6a018](https://github.com/ReventlessDev/reventless-core/commit/fc6a01811ba788f3289e84a435a09f77fa627e1a))
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **core:** references derived from identities, and identity checks ([7cb1376](https://github.com/ReventlessDev/reventless-core/commit/7cb1376e56cdb4e4d5ab5954cd97fd37fd3ba258))
|
|
12
|
+
|
|
13
|
+
### BREAKING CHANGES
|
|
14
|
+
|
|
15
|
+
* a projection that keys a row by a payload `string`, or uses
|
|
16
|
+
the envelope id as a `string`, converts explicitly with
|
|
17
|
+
`Target.Id.makeFromString` / `Source.Id.toString`. `StateChangeSlice.Spec` no
|
|
18
|
+
longer requires `module Id`.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
6
22
|
# 3.0.0-alpha.139 (2026-09-21)
|
|
7
23
|
|
|
8
24
|
### Features
|
package/package.json
CHANGED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Where a plugin's ids and their types disagree. Needs the whole plugin, not one
|
|
3
|
+
file: whether `orderId` names an identity depends on whether any field is typed
|
|
4
|
+
`OrderId.t`, which is also what makes adoption opt-in per plugin — a plugin that
|
|
5
|
+
types nothing is never reported.
|
|
6
|
+
|
|
7
|
+
- **A field named for one identity and typed as another** (`orderId: CustomerId.t`):
|
|
8
|
+
the mix-up the types exist to catch. A role name that is no identity of its own
|
|
9
|
+
(`sellerId: CustomerId.t`) is fine.
|
|
10
|
+
- **An untyped `*Id` whose key has an identity** (`productId: string` beside a
|
|
11
|
+
`ProductId.t`): the half-migrated state, where the compiler guards some uses and
|
|
12
|
+
not others.
|
|
13
|
+
*/
|
|
14
|
+
type finding = {sliceName: string, message: string}
|
|
15
|
+
|
|
16
|
+
// The identity key a `*Id` / `*Ids` name gives by convention.
|
|
17
|
+
let nameKey = (name: string): option<string> =>
|
|
18
|
+
if name->String.endsWith("Ids") {
|
|
19
|
+
Some(name->String.slice(~start=0, ~end=name->String.length - 1))
|
|
20
|
+
} else if name->String.length > 2 && name->String.endsWith("Id") {
|
|
21
|
+
Some(name)
|
|
22
|
+
} else {
|
|
23
|
+
None
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let fieldsOf = (schema: S.t<unknown>): array<(string, S.t<unknown>)> => {
|
|
27
|
+
let ofObject = (v: S.t<unknown>) =>
|
|
28
|
+
switch v {
|
|
29
|
+
| Object({properties}) => properties->Dict.toArray->Array.filter(((n, _)) => n != "TAG")
|
|
30
|
+
| _ => []
|
|
31
|
+
}
|
|
32
|
+
switch schema {
|
|
33
|
+
| AnyOf({anyOf}) => anyOf->Array.flatMap(ofObject)
|
|
34
|
+
| other => ofObject(other)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let slicesFields = (slice: DcbTag.sliceSchemas) =>
|
|
39
|
+
[slice.commandSchema, slice.consumedEventSchema, slice.eventSchema]->Array.flatMap(fieldsOf)
|
|
40
|
+
|
|
41
|
+
let check = (slices: array<DcbTag.sliceSchemas>): array<finding> => {
|
|
42
|
+
let declared: Set.t<string> = Set.make()
|
|
43
|
+
slices->Array.forEach(slice =>
|
|
44
|
+
slice
|
|
45
|
+
->slicesFields
|
|
46
|
+
->Array.forEach(((_, schema)) =>
|
|
47
|
+
Semantic.fieldIdentityKey(schema)->Option.forEach(k => declared->Set.add(k))
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
let seen: Set.t<string> = Set.make()
|
|
51
|
+
let findings = []
|
|
52
|
+
slices->Array.forEach(slice =>
|
|
53
|
+
slice
|
|
54
|
+
->slicesFields
|
|
55
|
+
->Array.forEach(((name, schema)) => {
|
|
56
|
+
let message = switch (Semantic.fieldIdentityKey(schema), nameKey(name)) {
|
|
57
|
+
| (Some(key), Some(named)) if named != key && declared->Set.has(named) =>
|
|
58
|
+
Some(
|
|
59
|
+
`${name} is typed as a ${key}, but ${named} is an identity of its own in this plugin. Type it as ${named}'s identity, or rename the field.`,
|
|
60
|
+
)
|
|
61
|
+
| (None, Some(named)) if declared->Set.has(named) =>
|
|
62
|
+
Some(
|
|
63
|
+
`${name} is a plain string, but ${named} has an identity in this plugin. Type the field with it, so the compiler guards every use.`,
|
|
64
|
+
)
|
|
65
|
+
| _ => None
|
|
66
|
+
}
|
|
67
|
+
message->Option.forEach(
|
|
68
|
+
message => {
|
|
69
|
+
let id = slice.name ++ "\n" ++ message
|
|
70
|
+
if !(seen->Set.has(id)) {
|
|
71
|
+
seen->Set.add(id)
|
|
72
|
+
findings->Array.push({sliceName: slice.name, message})
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
})
|
|
77
|
+
)
|
|
78
|
+
findings
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
A slice partitioned by an identity another chapter declares: `categoryId`
|
|
83
|
+
partitioning a slice under `Product/`. The chapter is the default home of the
|
|
84
|
+
identity its slices decide about, so the two disagreeing usually means the slice
|
|
85
|
+
or the identity is in the wrong folder. `identityChapters` maps a key to the
|
|
86
|
+
chapter whose folder declares it; an identity declared in another package has
|
|
87
|
+
none and is not checked.
|
|
88
|
+
*/
|
|
89
|
+
let checkChapters = (
|
|
90
|
+
~identityChapters: dict<string>,
|
|
91
|
+
~partitionBySlice: dict<string>,
|
|
92
|
+
slices: array<DcbTag.sliceSchemas>,
|
|
93
|
+
): array<finding> =>
|
|
94
|
+
slices->Array.filterMap(slice =>
|
|
95
|
+
switch (
|
|
96
|
+
partitionBySlice->Dict.get(slice.name),
|
|
97
|
+
slice.moduleUrl->Option.flatMap(DcbTag.chapterOfModuleUrl),
|
|
98
|
+
) {
|
|
99
|
+
| (Some(key), Some(chapter)) =>
|
|
100
|
+
switch identityChapters->Dict.get(key) {
|
|
101
|
+
| Some(home) if home != chapter =>
|
|
102
|
+
Some({
|
|
103
|
+
sliceName: slice.name,
|
|
104
|
+
message: `is partitioned by ${key}, which ${home}/ declares, but sits in ${chapter}/. Move the slice or the identity so they share a chapter.`,
|
|
105
|
+
})
|
|
106
|
+
| _ => None
|
|
107
|
+
}
|
|
108
|
+
| _ => None
|
|
109
|
+
}
|
|
110
|
+
)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
import * as DcbTag$Reventless from "./DcbTag.res.mjs";
|
|
6
|
+
import * as Semantic$Reventless from "../semantic/Semantic.res.mjs";
|
|
7
|
+
|
|
8
|
+
function nameKey(name) {
|
|
9
|
+
if (name.endsWith("Ids")) {
|
|
10
|
+
return name.slice(0, name.length - 1 | 0);
|
|
11
|
+
} else if (name.length > 2 && name.endsWith("Id")) {
|
|
12
|
+
return name;
|
|
13
|
+
} else {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function fieldsOf(schema) {
|
|
19
|
+
let ofObject = v => {
|
|
20
|
+
if (v.type === "object") {
|
|
21
|
+
return Object.entries(v.properties).filter(param => param[0] !== "TAG");
|
|
22
|
+
} else {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
if (schema.type === "anyOf") {
|
|
27
|
+
return schema.anyOf.flatMap(ofObject);
|
|
28
|
+
} else {
|
|
29
|
+
return ofObject(schema);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function slicesFields(slice) {
|
|
34
|
+
return [
|
|
35
|
+
slice.commandSchema,
|
|
36
|
+
slice.consumedEventSchema,
|
|
37
|
+
slice.eventSchema
|
|
38
|
+
].flatMap(fieldsOf);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function check(slices) {
|
|
42
|
+
let declared = new Set();
|
|
43
|
+
slices.forEach(slice => {
|
|
44
|
+
slicesFields(slice).forEach(param => Stdlib_Option.forEach(Semantic$Reventless.fieldIdentityKey(param[1]), k => {
|
|
45
|
+
declared.add(k);
|
|
46
|
+
}));
|
|
47
|
+
});
|
|
48
|
+
let seen = new Set();
|
|
49
|
+
let findings = [];
|
|
50
|
+
slices.forEach(slice => {
|
|
51
|
+
slicesFields(slice).forEach(param => {
|
|
52
|
+
let name = param[0];
|
|
53
|
+
let match = Semantic$Reventless.fieldIdentityKey(param[1]);
|
|
54
|
+
let match$1 = nameKey(name);
|
|
55
|
+
let message = match !== undefined ? (
|
|
56
|
+
match$1 !== undefined && match$1 !== match && declared.has(match$1) ? name + ` is typed as a ` + match + `, but ` + match$1 + ` is an identity of its own in this plugin. Type it as ` + match$1 + `'s identity, or rename the field.` : undefined
|
|
57
|
+
) : (
|
|
58
|
+
match$1 !== undefined && declared.has(match$1) ? name + ` is a plain string, but ` + match$1 + ` has an identity in this plugin. Type the field with it, so the compiler guards every use.` : undefined
|
|
59
|
+
);
|
|
60
|
+
Stdlib_Option.forEach(message, message => {
|
|
61
|
+
let id = slice.name + "\n" + message;
|
|
62
|
+
if (!seen.has(id)) {
|
|
63
|
+
seen.add(id);
|
|
64
|
+
findings.push({
|
|
65
|
+
sliceName: slice.name,
|
|
66
|
+
message: message
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
return findings;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function checkChapters(identityChapters, partitionBySlice, slices) {
|
|
77
|
+
return Stdlib_Array.filterMap(slices, slice => {
|
|
78
|
+
let match = partitionBySlice[slice.name];
|
|
79
|
+
let match$1 = Stdlib_Option.flatMap(slice.moduleUrl, DcbTag$Reventless.chapterOfModuleUrl);
|
|
80
|
+
if (match === undefined) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (match$1 === undefined) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
let home = identityChapters[match];
|
|
87
|
+
if (home !== undefined && home !== match$1) {
|
|
88
|
+
return {
|
|
89
|
+
sliceName: slice.name,
|
|
90
|
+
message: `is partitioned by ` + match + `, which ` + home + `/ declares, but sits in ` + match$1 + `/. Move the slice or the identity so they share a chapter.`
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export {
|
|
97
|
+
nameKey,
|
|
98
|
+
fieldsOf,
|
|
99
|
+
slicesFields,
|
|
100
|
+
check,
|
|
101
|
+
checkChapters,
|
|
102
|
+
}
|
|
103
|
+
/* DcbTag-Reventless Not a pure module */
|
|
@@ -42,17 +42,15 @@ let decide = (state, command) => switch command {
|
|
|
42
42
|
```
|
|
43
43
|
*/
|
|
44
44
|
/**
|
|
45
|
-
The lean Spec for a StateChangeSlice — types
|
|
46
|
-
state-evolution functions live in the sibling `Behavior` module type.
|
|
45
|
+
The lean Spec for a StateChangeSlice — types and schemas. State and
|
|
46
|
+
state-evolution functions live in the sibling `Behavior` module type. A slice has
|
|
47
|
+
no identity of its own: it decides about the one its partition field carries.
|
|
47
48
|
*/
|
|
48
49
|
module type Spec = {
|
|
49
50
|
/** Logical name of this slice (used as a command topic prefix). */
|
|
50
51
|
let name: string
|
|
51
52
|
let moduleUrl: string
|
|
52
53
|
|
|
53
|
-
/** Identity type — always `Id.String` for DCB slices. */
|
|
54
|
-
module Id: Id.T
|
|
55
|
-
|
|
56
54
|
/**
|
|
57
55
|
Events this slice reads to build its decision model (in `evolve`).
|
|
58
56
|
Only needs the fields required for the decision — no tag annotations needed.
|
|
@@ -51,6 +51,14 @@ module type Spec = {
|
|
|
51
51
|
/** Sury schema for the state type — generated automatically by `@schema`. */
|
|
52
52
|
let stateSchema: S.t<state>
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
What a row is keyed by. `Id.StringPure` (a plain `string`) unless the view
|
|
56
|
+
declares its identity — `module Key = OrderId` — in which case a projection
|
|
57
|
+
that keys a row by another entity's id does not compile. Auto-injected by
|
|
58
|
+
`@@reventless.spec` in a `StateView/` folder.
|
|
59
|
+
*/
|
|
60
|
+
module Key: Id.T
|
|
61
|
+
|
|
54
62
|
/**
|
|
55
63
|
Events this view slice projects. Only needs the fields required for the projection —
|
|
56
64
|
no tag annotations needed. May be payload-less where only existence matters.
|
|
@@ -114,7 +122,7 @@ module type Projection = {
|
|
|
114
122
|
Receives the event wrapped in a `consumed` envelope (event + `meta` +
|
|
115
123
|
`recordedAt`); only events declared in `Spec.consumedEvent` — no wildcard needed.
|
|
116
124
|
*/
|
|
117
|
-
let project: consumed<Spec.consumedEvent> => array<Projection.action<
|
|
125
|
+
let project: consumed<Spec.consumedEvent> => array<Projection.action<Spec.Key.t, Spec.state>>
|
|
118
126
|
|
|
119
127
|
/** File URL of this Projection module (`import.meta.url`). */
|
|
120
128
|
let moduleUrl: string
|
|
@@ -258,6 +258,17 @@ let identityKey = (fieldSchema: S.t<'a>): option<string> =>
|
|
|
258
258
|
| _ => None
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
+
/** The identity a field names, on its own value or on an array's elements. */
|
|
262
|
+
let rec fieldIdentityKey = (fieldSchema: S.t<unknown>): option<string> =>
|
|
263
|
+
switch identityKey(fieldSchema) {
|
|
264
|
+
| Some(_) as found => found
|
|
265
|
+
| None =>
|
|
266
|
+
switch fieldSchema->unwrapOptional->Option.getOr(fieldSchema) {
|
|
267
|
+
| Array({additionalItems: Schema(item)}) => fieldIdentityKey(item)
|
|
268
|
+
| _ => None
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
261
272
|
/** Whether a field's schema carries this specific semantic. */
|
|
262
273
|
let has = (fieldSchema: S.t<'a>, ~id: string): bool =>
|
|
263
274
|
switch get(fieldSchema) {
|
|
@@ -225,6 +225,26 @@ function identityKey(fieldSchema) {
|
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
function fieldIdentityKey(_fieldSchema) {
|
|
229
|
+
while (true) {
|
|
230
|
+
let fieldSchema = _fieldSchema;
|
|
231
|
+
let found = identityKey(fieldSchema);
|
|
232
|
+
if (found !== undefined) {
|
|
233
|
+
return found;
|
|
234
|
+
}
|
|
235
|
+
let match = Stdlib_Option.getOr(unwrapOptional(fieldSchema), fieldSchema);
|
|
236
|
+
if (match.type !== "array") {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
let item = match.additionalItems;
|
|
240
|
+
if (item === "strip" || item === "strict") {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
_fieldSchema = item;
|
|
244
|
+
continue;
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
228
248
|
function has(fieldSchema, id) {
|
|
229
249
|
let s = getFrom(fieldSchema);
|
|
230
250
|
if (s !== undefined) {
|
|
@@ -246,6 +266,7 @@ export {
|
|
|
246
266
|
getFrom,
|
|
247
267
|
get,
|
|
248
268
|
identityKey,
|
|
269
|
+
fieldIdentityKey,
|
|
249
270
|
has,
|
|
250
271
|
}
|
|
251
272
|
/* semanticId Not a pure module */
|
package/src/types/Projection.res
CHANGED
|
@@ -79,23 +79,60 @@ type action<'id, 'state> =
|
|
|
79
79
|
/** No-op. Return this when an event should not affect the read model. */
|
|
80
80
|
| Ignore
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
The same action keyed by another representation of its ids. `to_` converts the
|
|
84
|
+
ids the action carries; `from` converts back the ids its callbacks receive. The
|
|
85
|
+
storage edge uses it to turn a typed row key into the string a table is keyed by.
|
|
86
|
+
*/
|
|
87
|
+
let mapActionId = (action: action<'a, 's>, ~to_: 'a => 'b, ~from: 'b => 'a): action<'b, 's> =>
|
|
88
|
+
switch action {
|
|
89
|
+
| Create(id, state) => Create(to_(id), state)
|
|
90
|
+
| CreateMany(rows) => CreateMany(rows->Array.map(((id, state)) => (to_(id), state)))
|
|
91
|
+
| Update(id, f) => Update(to_(id), f)
|
|
92
|
+
| UpdateMany(ids, f) => UpdateMany(ids->Array.map(to_), (id, state) => f(from(id), state))
|
|
93
|
+
| UpdateWithDefault(id, default, f) => UpdateWithDefault(to_(id), default, f)
|
|
94
|
+
| UpdateManyWithDefault(ids, default, f) =>
|
|
95
|
+
UpdateManyWithDefault(
|
|
96
|
+
ids->Array.map(to_),
|
|
97
|
+
id => default(from(id)),
|
|
98
|
+
(id, state) => f(from(id), state),
|
|
99
|
+
)
|
|
100
|
+
| Set(id, state) => Set(to_(id), state)
|
|
101
|
+
| SetMany(ids, f) => SetMany(ids->Array.map(to_), id => f(from(id)))
|
|
102
|
+
| Delete(id) => Delete(to_(id))
|
|
103
|
+
| DeleteMany(ids) => DeleteMany(ids->Array.map(to_))
|
|
104
|
+
| DeleteIf(id, p) => DeleteIf(to_(id), p)
|
|
105
|
+
| DeleteManyIf(ids, p) => DeleteManyIf(ids->Array.map(to_), (id, state) => p(from(id), state))
|
|
106
|
+
| CreateMultiState(id, states) => CreateMultiState(to_(id), states)
|
|
107
|
+
| UpdateMultiState(id, f) => UpdateMultiState(to_(id), f)
|
|
108
|
+
| UpdateManyMultiStates(ids, f) =>
|
|
109
|
+
UpdateManyMultiStates(ids->Array.map(to_), (id, states) => f(from(id), states))
|
|
110
|
+
| Ignore => Ignore
|
|
111
|
+
}
|
|
112
|
+
|
|
82
113
|
/**
|
|
83
114
|
A compiled single-source-to-single-target mapping.
|
|
84
115
|
|
|
85
116
|
Created by `Projection.Mapping.Make(Source, Target, MappingImpl)`.
|
|
86
117
|
The `project` function receives a full `Message.event'` envelope and returns
|
|
87
|
-
one `action` value.
|
|
118
|
+
one `action` value. Both ends are typed: the envelope id is the source's `Id.t`
|
|
119
|
+
and the row key the target's, so a projection that keys a view by another
|
|
120
|
+
entity's id does not compile. A mapping between different ids converts
|
|
121
|
+
explicitly (`Target.Id.makeFromString`), which marks the seam.
|
|
88
122
|
*/
|
|
89
123
|
module type Mapping = {
|
|
90
124
|
//module Source: Source
|
|
91
125
|
//module Target: Target // NOTE: to be destructive substituted
|
|
92
126
|
module SourceId: Id.T
|
|
127
|
+
type targetId
|
|
93
128
|
@schema
|
|
94
129
|
type sourceEvent
|
|
95
130
|
@schema
|
|
96
131
|
type targetState
|
|
97
132
|
|
|
98
|
-
let project: Message.event'<
|
|
133
|
+
let project: Message.event'<SourceId.t, sourceEvent> => action<targetId, targetState>
|
|
134
|
+
let targetIdToString: targetId => string
|
|
135
|
+
let targetIdFromString: string => targetId
|
|
99
136
|
let sourceEventSchema: S.t<sourceEvent>
|
|
100
137
|
let sourceName: string
|
|
101
138
|
let subIdConfig: option<ReadModel.subIdConfig<targetState>>
|
|
@@ -116,9 +153,11 @@ module type Mappings = {
|
|
|
116
153
|
}
|
|
117
154
|
|
|
118
155
|
module type MappingImpl = {
|
|
156
|
+
type sourceId
|
|
157
|
+
type targetId
|
|
119
158
|
type sourceEvent
|
|
120
159
|
type targetState
|
|
121
|
-
let project: Message.event'<
|
|
160
|
+
let project: Message.event'<sourceId, sourceEvent> => action<targetId, targetState>
|
|
122
161
|
}
|
|
123
162
|
|
|
124
163
|
/**
|
|
@@ -147,7 +186,9 @@ module Mapping = {
|
|
|
147
186
|
Target: Target,
|
|
148
187
|
MappingImpl: MappingImpl
|
|
149
188
|
with type sourceEvent := Source.event
|
|
150
|
-
and type targetState := Target.state
|
|
189
|
+
and type targetState := Target.state
|
|
190
|
+
and type sourceId := Source.Id.t
|
|
191
|
+
and type targetId := Target.Id.t,
|
|
151
192
|
): (
|
|
152
193
|
Mapping
|
|
153
194
|
with type targetState = Target.state
|
|
@@ -155,11 +196,14 @@ module Mapping = {
|
|
|
155
196
|
and module SourceId = Source.Id
|
|
156
197
|
) => {
|
|
157
198
|
module SourceId = Source.Id
|
|
199
|
+
type targetId = Target.Id.t
|
|
158
200
|
@schema
|
|
159
201
|
type sourceEvent = Source.event
|
|
160
202
|
@schema
|
|
161
203
|
type targetState = Target.state
|
|
162
204
|
let project = MappingImpl.project
|
|
205
|
+
let targetIdToString = Target.Id.toString
|
|
206
|
+
let targetIdFromString = Target.Id.makeFromString
|
|
163
207
|
let sourceName = Source.name
|
|
164
208
|
let subIdConfig = Target.subIdConfig
|
|
165
209
|
}
|
|
@@ -1,15 +1,127 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
function mapActionId(action, to_, from) {
|
|
5
|
+
if (typeof action !== "object") {
|
|
6
|
+
return "Ignore";
|
|
7
|
+
}
|
|
8
|
+
switch (action.TAG) {
|
|
9
|
+
case "Create" :
|
|
10
|
+
return {
|
|
11
|
+
TAG: "Create",
|
|
12
|
+
_0: to_(action._0),
|
|
13
|
+
_1: action._1
|
|
14
|
+
};
|
|
15
|
+
case "CreateMany" :
|
|
16
|
+
return {
|
|
17
|
+
TAG: "CreateMany",
|
|
18
|
+
_0: action._0.map(param => [
|
|
19
|
+
to_(param[0]),
|
|
20
|
+
param[1]
|
|
21
|
+
])
|
|
22
|
+
};
|
|
23
|
+
case "Update" :
|
|
24
|
+
return {
|
|
25
|
+
TAG: "Update",
|
|
26
|
+
_0: to_(action._0),
|
|
27
|
+
_1: action._1
|
|
28
|
+
};
|
|
29
|
+
case "UpdateMany" :
|
|
30
|
+
let f = action._1;
|
|
31
|
+
return {
|
|
32
|
+
TAG: "UpdateMany",
|
|
33
|
+
_0: action._0.map(to_),
|
|
34
|
+
_1: (id, state) => f(from(id), state)
|
|
35
|
+
};
|
|
36
|
+
case "UpdateWithDefault" :
|
|
37
|
+
return {
|
|
38
|
+
TAG: "UpdateWithDefault",
|
|
39
|
+
_0: to_(action._0),
|
|
40
|
+
_1: action._1,
|
|
41
|
+
_2: action._2
|
|
42
|
+
};
|
|
43
|
+
case "UpdateManyWithDefault" :
|
|
44
|
+
let f$1 = action._2;
|
|
45
|
+
let $$default = action._1;
|
|
46
|
+
return {
|
|
47
|
+
TAG: "UpdateManyWithDefault",
|
|
48
|
+
_0: action._0.map(to_),
|
|
49
|
+
_1: id => $$default(from(id)),
|
|
50
|
+
_2: (id, state) => f$1(from(id), state)
|
|
51
|
+
};
|
|
52
|
+
case "Set" :
|
|
53
|
+
return {
|
|
54
|
+
TAG: "Set",
|
|
55
|
+
_0: to_(action._0),
|
|
56
|
+
_1: action._1
|
|
57
|
+
};
|
|
58
|
+
case "SetMany" :
|
|
59
|
+
let f$2 = action._1;
|
|
60
|
+
return {
|
|
61
|
+
TAG: "SetMany",
|
|
62
|
+
_0: action._0.map(to_),
|
|
63
|
+
_1: id => f$2(from(id))
|
|
64
|
+
};
|
|
65
|
+
case "Delete" :
|
|
66
|
+
return {
|
|
67
|
+
TAG: "Delete",
|
|
68
|
+
_0: to_(action._0)
|
|
69
|
+
};
|
|
70
|
+
case "DeleteMany" :
|
|
71
|
+
return {
|
|
72
|
+
TAG: "DeleteMany",
|
|
73
|
+
_0: action._0.map(to_)
|
|
74
|
+
};
|
|
75
|
+
case "DeleteIf" :
|
|
76
|
+
return {
|
|
77
|
+
TAG: "DeleteIf",
|
|
78
|
+
_0: to_(action._0),
|
|
79
|
+
_1: action._1
|
|
80
|
+
};
|
|
81
|
+
case "DeleteManyIf" :
|
|
82
|
+
let p = action._1;
|
|
83
|
+
return {
|
|
84
|
+
TAG: "DeleteManyIf",
|
|
85
|
+
_0: action._0.map(to_),
|
|
86
|
+
_1: (id, state) => p(from(id), state)
|
|
87
|
+
};
|
|
88
|
+
case "CreateMultiState" :
|
|
89
|
+
return {
|
|
90
|
+
TAG: "CreateMultiState",
|
|
91
|
+
_0: to_(action._0),
|
|
92
|
+
_1: action._1
|
|
93
|
+
};
|
|
94
|
+
case "UpdateMultiState" :
|
|
95
|
+
return {
|
|
96
|
+
TAG: "UpdateMultiState",
|
|
97
|
+
_0: to_(action._0),
|
|
98
|
+
_1: action._1
|
|
99
|
+
};
|
|
100
|
+
case "UpdateManyMultiStates" :
|
|
101
|
+
let f$3 = action._1;
|
|
102
|
+
return {
|
|
103
|
+
TAG: "UpdateManyMultiStates",
|
|
104
|
+
_0: action._0.map(to_),
|
|
105
|
+
_1: (id, states) => f$3(from(id), states)
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
4
110
|
function Make(Source) {
|
|
5
|
-
return Target => (MappingImpl =>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
111
|
+
return Target => (MappingImpl => {
|
|
112
|
+
let targetIdToString = Target.Id.toString;
|
|
113
|
+
let targetIdFromString = Target.Id.makeFromString;
|
|
114
|
+
return {
|
|
115
|
+
SourceId: Source.Id,
|
|
116
|
+
project: MappingImpl.project,
|
|
117
|
+
targetIdToString: targetIdToString,
|
|
118
|
+
targetIdFromString: targetIdFromString,
|
|
119
|
+
sourceEventSchema: Source.eventSchema,
|
|
120
|
+
sourceName: Source.name,
|
|
121
|
+
subIdConfig: Target.subIdConfig,
|
|
122
|
+
targetStateSchema: Target.stateSchema
|
|
123
|
+
};
|
|
124
|
+
});
|
|
13
125
|
}
|
|
14
126
|
|
|
15
127
|
let Mapping = {
|
|
@@ -37,6 +149,7 @@ let DcbSource = {
|
|
|
37
149
|
};
|
|
38
150
|
|
|
39
151
|
export {
|
|
152
|
+
mapActionId,
|
|
40
153
|
Mapping,
|
|
41
154
|
Mappings,
|
|
42
155
|
DcbSource,
|