@reventlessdev/reventless-core 3.0.0-alpha.244 → 3.0.0-alpha.245
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
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
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.245 (2026-08-20)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **core:** report a union value stored without its member type ([3a834b5](https://github.com/ReventlessDev/reventless-core/commit/3a834b51d1d9441f5dfcaebec5fac365063c905e))
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
# 3.0.0-alpha.244 (2026-08-20)
|
|
7
14
|
|
|
8
15
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
let log = Logger.fromEnv()
|
|
2
|
+
|
|
1
3
|
module type Ops = {
|
|
2
4
|
let jsonOps: QueryDb.operations<string, JSON.t>
|
|
3
5
|
}
|
|
@@ -72,11 +74,53 @@ module Make = (ReadModelSpec: Reventless.ReadModel.Spec, Ops: Ops) => {
|
|
|
72
74
|
// backends — and, unlike a read-time stamp, it is also on the row the live
|
|
73
75
|
// change channel hands over as raw JSON. A view with no union field is walked
|
|
74
76
|
// and left exactly as it was.
|
|
75
|
-
|
|
77
|
+
// The union fields this view declares, read once. Empty for most views, and
|
|
78
|
+
// the whole diagnostic below costs nothing when it is.
|
|
79
|
+
let declaredUnionFields =
|
|
80
|
+
Reventless.TaggedUnion.fieldsOf(ReadModelSpec.stateSchema->S.castToUnknown)
|
|
81
|
+
|
|
82
|
+
// Logged once per instantiation, and unconditionally: "none" has to be
|
|
83
|
+
// distinguishable from "never instantiated", because a schema that resolves no
|
|
84
|
+
// union fields and a stamp that failed to run leave the same stored row.
|
|
85
|
+
log.debug(
|
|
86
|
+
~comp="QueryDb",
|
|
87
|
+
`${ReadModelSpec.name}: union fields ${switch declaredUnionFields {
|
|
88
|
+
| [] => "none"
|
|
89
|
+
| fields =>
|
|
90
|
+
fields->Array.map(((f, n)) => `${f}:${n->Option.getOr("<unnamed>")}`)->Array.join(", ")
|
|
91
|
+
}}`,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
// A union value stored without `__typename` is the failure D1 exists to
|
|
95
|
+
// prevent, and it is silent: the member resolves to null, the null takes its
|
|
96
|
+
// non-nullable parent, and a list loses the row rather than erroring. Nothing
|
|
97
|
+
// downstream can tell that from "no such row", so it is reported here — the
|
|
98
|
+
// one place that knows both the schema and the row.
|
|
99
|
+
let reportUnstamped = dict =>
|
|
100
|
+
declaredUnionFields->Array.forEach(((field, unionName)) =>
|
|
101
|
+
switch dict->Dict.get(field)->Option.flatMap(JSON.Decode.object) {
|
|
102
|
+
| Some(value) if value->Dict.get("TAG")->Option.isSome =>
|
|
103
|
+
if value->Dict.get(Reventless.TaggedUnion.typenameKey)->Option.isNone {
|
|
104
|
+
log.warn(
|
|
105
|
+
~comp="QueryDb",
|
|
106
|
+
`${ReadModelSpec.name}.${field}: stored a union value with no ${Reventless.TaggedUnion.typenameKey} ` ++
|
|
107
|
+
`(union ${unionName->Option.getOr("<unnamed>")}). The field will resolve to null and take its ` ++
|
|
108
|
+
`parent with it. Union fields seen on this spec: ${declaredUnionFields
|
|
109
|
+
->Array.map(((f, _)) => f)
|
|
110
|
+
->Array.join(", ")}`,
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
| _ => ()
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
let stampUnionMembers = dict => {
|
|
76
118
|
Reventless.TaggedUnion.stampInto(
|
|
77
119
|
~schema=ReadModelSpec.stateSchema->S.castToUnknown,
|
|
78
120
|
JSON.Encode.object(dict),
|
|
79
121
|
)
|
|
122
|
+
reportUnstamped(dict)
|
|
123
|
+
}
|
|
80
124
|
|
|
81
125
|
let save = async (id, state, saveMode, ttl) =>
|
|
82
126
|
switch state->Message.encode(ReadModelSpec.stateSchema)->JSON.Decode.object {
|
|
@@ -7,9 +7,12 @@ import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
|
|
|
7
7
|
import * as Effect from "effect/Effect";
|
|
8
8
|
import * as Stream from "effect/Stream";
|
|
9
9
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
10
|
+
import * as Logger$ReventlessCore from "../../util/Logger.res.mjs";
|
|
10
11
|
import * as Message$ReventlessCore from "../../Message.res.mjs";
|
|
11
12
|
import * as TaggedUnion$Reventless from "@reventlessdev/reventless-spec/src/components/TaggedUnion.res.mjs";
|
|
12
13
|
|
|
14
|
+
let log = Logger$ReventlessCore.fromEnv();
|
|
15
|
+
|
|
13
16
|
function Make(ReadModelSpec) {
|
|
14
17
|
return Ops => {
|
|
15
18
|
let decode = (id, stateJson) => {
|
|
@@ -77,14 +80,30 @@ function Make(ReadModelSpec) {
|
|
|
77
80
|
dict[attrName$1] = value$1;
|
|
78
81
|
});
|
|
79
82
|
};
|
|
80
|
-
let
|
|
83
|
+
let declaredUnionFields = TaggedUnion$Reventless.fieldsOf(ReadModelSpec.stateSchema);
|
|
84
|
+
log.debug("QueryDb", undefined, ReadModelSpec.name + `: union fields ` + (
|
|
85
|
+
declaredUnionFields.length !== 0 ? declaredUnionFields.map(param => param[0] + `:` + Stdlib_Option.getOr(param[1], "<unnamed>")).join(", ") : "none"
|
|
86
|
+
));
|
|
87
|
+
let reportUnstamped = dict => {
|
|
88
|
+
declaredUnionFields.forEach(param => {
|
|
89
|
+
let field = param[0];
|
|
90
|
+
let value = Stdlib_Option.flatMap(dict[field], Stdlib_JSON.Decode.object);
|
|
91
|
+
if (value !== undefined && Stdlib_Option.isSome(value["TAG"]) && Stdlib_Option.isNone(value[TaggedUnion$Reventless.typenameKey])) {
|
|
92
|
+
return log.warn("QueryDb", undefined, ReadModelSpec.name + `.` + field + `: stored a union value with no ` + TaggedUnion$Reventless.typenameKey + ` ` + (`(union ` + Stdlib_Option.getOr(param[1], "<unnamed>") + `). The field will resolve to null and take its `) + (`parent with it. Union fields seen on this spec: ` + declaredUnionFields.map(param => param[0]).join(", ")));
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
let stampUnionMembers = dict => {
|
|
97
|
+
TaggedUnion$Reventless.stampInto(ReadModelSpec.stateSchema, dict);
|
|
98
|
+
reportUnstamped(dict);
|
|
99
|
+
};
|
|
81
100
|
let save = async (id, state, saveMode, ttl) => {
|
|
82
101
|
let dict = Stdlib_JSON.Decode.object(Message$ReventlessCore.encode(state, ReadModelSpec.stateSchema));
|
|
83
102
|
if (dict !== undefined) {
|
|
84
103
|
dict["id"] = Message$ReventlessCore.encode(id, ReadModelSpec.Id.schema);
|
|
85
104
|
injectSubId(dict, state);
|
|
86
105
|
injectCompositeIndexAttrs(dict);
|
|
87
|
-
|
|
106
|
+
stampUnionMembers(dict);
|
|
88
107
|
return await Ops.jsonOps.save(ReadModelSpec.Id.toString(id), dict, saveMode, ttl);
|
|
89
108
|
} else {
|
|
90
109
|
return {
|
|
@@ -110,7 +129,7 @@ function Make(ReadModelSpec) {
|
|
|
110
129
|
dict["id"] = Message$ReventlessCore.encode(id, ReadModelSpec.Id.schema);
|
|
111
130
|
injectSubId(dict, state);
|
|
112
131
|
injectCompositeIndexAttrs(dict);
|
|
113
|
-
|
|
132
|
+
stampUnionMembers(dict);
|
|
114
133
|
return {
|
|
115
134
|
TAG: "Ok",
|
|
116
135
|
_0: batch.concat([[
|
|
@@ -154,6 +173,8 @@ function Make(ReadModelSpec) {
|
|
|
154
173
|
load: load,
|
|
155
174
|
injectSubId: injectSubId,
|
|
156
175
|
injectCompositeIndexAttrs: injectCompositeIndexAttrs,
|
|
176
|
+
declaredUnionFields: declaredUnionFields,
|
|
177
|
+
reportUnstamped: reportUnstamped,
|
|
157
178
|
stampUnionMembers: stampUnionMembers,
|
|
158
179
|
save: save,
|
|
159
180
|
saveBatch: saveBatch,
|
|
@@ -165,6 +186,7 @@ function Make(ReadModelSpec) {
|
|
|
165
186
|
}
|
|
166
187
|
|
|
167
188
|
export {
|
|
189
|
+
log,
|
|
168
190
|
Make,
|
|
169
191
|
}
|
|
170
|
-
/*
|
|
192
|
+
/* log Not a pure module */
|