@reventlessdev/reventless-core 3.0.0-alpha.241 → 3.0.0-alpha.242
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 +7 -0
- package/package.json +6 -6
- package/src/components/Api/GraphQL_FragmentGenerator.res +49 -1
- package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +23 -1
- package/src/components/Api/SchemaType.res +133 -1
- package/src/components/Api/SchemaType.res.mjs +133 -8
- package/src/components/Api/SuryToJsonSchema.res +36 -0
- package/src/components/Api/SuryToJsonSchema.res.mjs +45 -0
- package/src/components/QueryDb/QueryDb_Operations.res +13 -0
- package/src/components/QueryDb/QueryDb_Operations.res.mjs +5 -0
- package/src/plugin/component/Plugin_Structure.res +9 -0
- package/src/plugin/component/Plugin_Structure.res.mjs +6 -4
- package/src/plugin/component/SchemaWalker.res +16 -3
- package/src/plugin/component/SchemaWalker.res.mjs +17 -6
- package/src/plugin/lifecycle/PluginsReadModelSpec.res +1 -1
- package/tests/admin/Platform_Admin_StructureTest.res +2 -2
- package/tests/api/TaggedUnionSchemaTest.res +198 -0
- package/tests/api/TaggedUnionSchemaTest.res.mjs +176 -0
- package/tests/fixtures/TaggedUnionFixtures.res +88 -0
- package/tests/fixtures/TaggedUnionFixtures.res.mjs +117 -0
- package/tests/querydb/TaggedUnionStampTest.res +163 -0
- package/tests/querydb/TaggedUnionStampTest.res.mjs +267 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Sury from "sury";
|
|
4
|
+
import * as GeoPoint$Reventless from "@reventlessdev/reventless-spec/src/semantic/GeoPoint.res.mjs";
|
|
5
|
+
import * as ReadModel$Reventless from "@reventlessdev/reventless-spec/src/components/ReadModel.res.mjs";
|
|
6
|
+
import * as TaggedUnion$Reventless from "@reventlessdev/reventless-spec/src/components/TaggedUnion.res.mjs";
|
|
7
|
+
|
|
8
|
+
let geolocationSchema = Sury.union([
|
|
9
|
+
Sury.$schema(s => ({
|
|
10
|
+
TAG: "Pending",
|
|
11
|
+
requestedFor: s.m(Sury.string)
|
|
12
|
+
})),
|
|
13
|
+
Sury.$schema(s => ({
|
|
14
|
+
TAG: "Located",
|
|
15
|
+
point: s.m(GeoPoint$Reventless.schema)
|
|
16
|
+
})),
|
|
17
|
+
Sury.$schema(s => ({
|
|
18
|
+
TAG: "Unresolvable",
|
|
19
|
+
reason: s.m(Sury.string)
|
|
20
|
+
}))
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
let geolocationSchema$1 = TaggedUnion$Reventless.named("Geolocation", geolocationSchema);
|
|
24
|
+
|
|
25
|
+
let moduleUrl = import.meta.url;
|
|
26
|
+
|
|
27
|
+
let stateSchema = Sury.$schema(s => ({
|
|
28
|
+
customerId: s.m(Sury.string),
|
|
29
|
+
geolocation: s.m(geolocationSchema$1)
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
let config = ReadModel$Reventless.config(undefined, undefined, undefined);
|
|
33
|
+
|
|
34
|
+
let CustomersSpec = {
|
|
35
|
+
Id: undefined,
|
|
36
|
+
name: "TaggedUnionCustomers",
|
|
37
|
+
moduleUrl: moduleUrl,
|
|
38
|
+
stateSchema: stateSchema,
|
|
39
|
+
config: config,
|
|
40
|
+
subIdConfig: undefined,
|
|
41
|
+
authorization: "AllowAuthenticated",
|
|
42
|
+
visibility: "Public"
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
let moduleUrl$1 = import.meta.url;
|
|
46
|
+
|
|
47
|
+
let stateSchema$1 = Sury.$schema(s => ({
|
|
48
|
+
sightingId: s.m(Sury.string),
|
|
49
|
+
lastSeen: s.m(Sury.$option(geolocationSchema$1)),
|
|
50
|
+
history: s.m(Sury.array(geolocationSchema$1))
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
let config$1 = ReadModel$Reventless.config(undefined, undefined, undefined);
|
|
54
|
+
|
|
55
|
+
let SightingsSpec = {
|
|
56
|
+
Id: undefined,
|
|
57
|
+
name: "TaggedUnionSightings",
|
|
58
|
+
moduleUrl: moduleUrl$1,
|
|
59
|
+
stateSchema: stateSchema$1,
|
|
60
|
+
config: config$1,
|
|
61
|
+
subIdConfig: undefined,
|
|
62
|
+
authorization: "AllowAuthenticated",
|
|
63
|
+
visibility: "Public"
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
let moduleUrl$2 = import.meta.url;
|
|
67
|
+
|
|
68
|
+
let verdictSchema = Sury.union([
|
|
69
|
+
Sury.$schema(s => ({
|
|
70
|
+
TAG: "Approved",
|
|
71
|
+
by: s.m(Sury.string)
|
|
72
|
+
})),
|
|
73
|
+
Sury.$schema(s => ({
|
|
74
|
+
TAG: "Rejected",
|
|
75
|
+
reason: s.m(Sury.string)
|
|
76
|
+
}))
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
let stateSchema$2 = Sury.$schema(s => ({
|
|
80
|
+
caseId: s.m(Sury.string),
|
|
81
|
+
verdict: s.m(verdictSchema)
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
let config$2 = ReadModel$Reventless.config(undefined, undefined, undefined);
|
|
85
|
+
|
|
86
|
+
let UnnamedSpec = {
|
|
87
|
+
Id: undefined,
|
|
88
|
+
name: "TaggedUnionUnnamed",
|
|
89
|
+
moduleUrl: moduleUrl$2,
|
|
90
|
+
verdictSchema: verdictSchema,
|
|
91
|
+
stateSchema: stateSchema$2,
|
|
92
|
+
config: config$2,
|
|
93
|
+
subIdConfig: undefined,
|
|
94
|
+
authorization: "AllowAuthenticated",
|
|
95
|
+
visibility: "Public"
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
function queryEntryFor(returnTypeName, specName, stateSchema) {
|
|
99
|
+
return {
|
|
100
|
+
singleFieldName: returnTypeName,
|
|
101
|
+
listFieldName: returnTypeName + "s",
|
|
102
|
+
returnTypeName: returnTypeName,
|
|
103
|
+
stateSchema: stateSchema,
|
|
104
|
+
specName: specName,
|
|
105
|
+
authorization: undefined,
|
|
106
|
+
connectionSpec: true
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export {
|
|
111
|
+
geolocationSchema$1 as geolocationSchema,
|
|
112
|
+
CustomersSpec,
|
|
113
|
+
SightingsSpec,
|
|
114
|
+
UnnamedSpec,
|
|
115
|
+
queryEntryFor,
|
|
116
|
+
}
|
|
117
|
+
/* geolocationSchema Not a pure module */
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
open JestGlobals
|
|
2
|
+
open QueryDbFixtures
|
|
3
|
+
open TaggedUnionFixtures
|
|
4
|
+
|
|
5
|
+
// The write-time `__typename` stamp, and the storage round trip it has to
|
|
6
|
+
// survive. Both AppSync and graphql-js resolve a union member from `__typename`
|
|
7
|
+
// on the value they are handed; the AppSync resolvers hand back the stored item
|
|
8
|
+
// unchanged, and the live change channel carries the row as raw JSON past the
|
|
9
|
+
// typed field entirely. So the stamp lands here, once, and everything downstream
|
|
10
|
+
// reads a row that already says which arm it is.
|
|
11
|
+
|
|
12
|
+
module Customers = QueryDb_Operations.Make(
|
|
13
|
+
CustomersSpec,
|
|
14
|
+
{
|
|
15
|
+
let jsonOps = mockJsonOps
|
|
16
|
+
},
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
module Sightings = QueryDb_Operations.Make(
|
|
20
|
+
SightingsSpec,
|
|
21
|
+
{
|
|
22
|
+
let jsonOps = mockJsonOps
|
|
23
|
+
},
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
module Items = QueryDb_Operations.Make(
|
|
27
|
+
ItemQueryDbSpec,
|
|
28
|
+
{
|
|
29
|
+
let jsonOps = mockJsonOps
|
|
30
|
+
},
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
let _ = beforeEach(() => reset())
|
|
34
|
+
|
|
35
|
+
let storedRow = id =>
|
|
36
|
+
store.contents->Dict.get(id)->Option.getOr([])->Array.get(0)->Option.getOr(JSON.Encode.null)
|
|
37
|
+
|
|
38
|
+
let field = (json, name) =>
|
|
39
|
+
json->JSON.Decode.object->Option.flatMap(o => o->Dict.get(name))->Option.getOr(JSON.Encode.null)
|
|
40
|
+
|
|
41
|
+
let string = json => json->JSON.Decode.string
|
|
42
|
+
|
|
43
|
+
describe("QueryDb_Operations stamps union members", () => {
|
|
44
|
+
testPromise("the stored value carries __typename beside TAG", async () => {
|
|
45
|
+
let state: CustomersSpec.state = {
|
|
46
|
+
customerId: "c-1",
|
|
47
|
+
geolocation: Located({point: {lat: 48.2082, lng: 16.3738}}),
|
|
48
|
+
}
|
|
49
|
+
let _ = await Customers.save("c-1", state, Init, None)
|
|
50
|
+
let geo = storedRow("c-1")->field("geolocation")
|
|
51
|
+
expect(geo->field("TAG")->string)->toEqual(Some("Located"))
|
|
52
|
+
expect(geo->field("__typename")->string)->toEqual(Some("GeolocationLocated"))
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
testPromise("every arm is stamped as the member type it is", async () => {
|
|
56
|
+
let _ = await Customers.save(
|
|
57
|
+
"c-2",
|
|
58
|
+
{customerId: "c-2", geolocation: Pending({requestedFor: "1 High St"})},
|
|
59
|
+
Init,
|
|
60
|
+
None,
|
|
61
|
+
)
|
|
62
|
+
let _ = await Customers.save(
|
|
63
|
+
"c-3",
|
|
64
|
+
{customerId: "c-3", geolocation: Unresolvable({reason: "ambiguous"})},
|
|
65
|
+
Init,
|
|
66
|
+
None,
|
|
67
|
+
)
|
|
68
|
+
expect(storedRow("c-2")->field("geolocation")->field("__typename")->string)->toEqual(
|
|
69
|
+
Some("GeolocationPending"),
|
|
70
|
+
)
|
|
71
|
+
expect(storedRow("c-3")->field("geolocation")->field("__typename")->string)->toEqual(
|
|
72
|
+
Some("GeolocationUnresolvable"),
|
|
73
|
+
)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
testPromise("an optional union and an array of them are stamped too", async () => {
|
|
77
|
+
let state: SightingsSpec.state = {
|
|
78
|
+
sightingId: "s-1",
|
|
79
|
+
lastSeen: Some(Located({point: {lat: 1.0, lng: 2.0}})),
|
|
80
|
+
history: [Pending({requestedFor: "somewhere"}), Unresolvable({reason: "gone"})],
|
|
81
|
+
}
|
|
82
|
+
let _ = await Sightings.save("s-1", state, Init, None)
|
|
83
|
+
let row = storedRow("s-1")
|
|
84
|
+
expect(row->field("lastSeen")->field("__typename")->string)->toEqual(
|
|
85
|
+
Some("GeolocationLocated"),
|
|
86
|
+
)
|
|
87
|
+
let history = row->field("history")->JSON.Decode.array->Option.getOr([])
|
|
88
|
+
expect(history->Array.map(h => h->field("__typename")->string))->toEqual([
|
|
89
|
+
Some("GeolocationPending"),
|
|
90
|
+
Some("GeolocationUnresolvable"),
|
|
91
|
+
])
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
testPromise("an absent optional union leaves nothing behind", async () => {
|
|
95
|
+
let _ = await Sightings.save(
|
|
96
|
+
"s-2",
|
|
97
|
+
{sightingId: "s-2", lastSeen: None, history: []},
|
|
98
|
+
Init,
|
|
99
|
+
None,
|
|
100
|
+
)
|
|
101
|
+
expect(storedRow("s-2")->field("lastSeen"))->toEqual(JSON.Encode.null)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// What keeps this releasable ahead of any adopter: a view with no union field
|
|
105
|
+
// is walked and written exactly as it was written before the walk existed.
|
|
106
|
+
testPromise("a view with no union field stores what it always stored", async () => {
|
|
107
|
+
let _ = await Items.save("item-1", {name: "Widget", count: 5}, Init, None)
|
|
108
|
+
expect(storedRow("item-1")->JSON.stringify)->toBe(
|
|
109
|
+
`{"name":"Widget","count":5,"id":"item-1"}`,
|
|
110
|
+
)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
testPromise("saveBatch stamps every row", async () => {
|
|
114
|
+
let batch = [
|
|
115
|
+
(
|
|
116
|
+
"c-4",
|
|
117
|
+
({customerId: "c-4", geolocation: Located({point: {lat: 0.0, lng: 0.0}})}: CustomersSpec.state),
|
|
118
|
+
None,
|
|
119
|
+
),
|
|
120
|
+
(
|
|
121
|
+
"c-5",
|
|
122
|
+
({customerId: "c-5", geolocation: Pending({requestedFor: "x"})}: CustomersSpec.state),
|
|
123
|
+
None,
|
|
124
|
+
),
|
|
125
|
+
]
|
|
126
|
+
let _ = await Customers.saveBatch(batch)
|
|
127
|
+
expect((
|
|
128
|
+
storedRow("c-4")->field("geolocation")->field("__typename")->string,
|
|
129
|
+
storedRow("c-5")->field("geolocation")->field("__typename")->string,
|
|
130
|
+
))->toEqual((Some("GeolocationLocated"), Some("GeolocationPending")))
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
describe("a stamped row decodes back", () => {
|
|
135
|
+
testPromise("the extra key does not disturb the parse", async () => {
|
|
136
|
+
let _ = await Customers.save(
|
|
137
|
+
"c-6",
|
|
138
|
+
{customerId: "c-6", geolocation: Located({point: {lat: 48.2082, lng: 16.3738}})},
|
|
139
|
+
Init,
|
|
140
|
+
None,
|
|
141
|
+
)
|
|
142
|
+
let result = await Customers.loadStream("c-6")->Stream.runCollect->Effect.runPromise
|
|
143
|
+
switch result {
|
|
144
|
+
| [{geolocation: Located({point})}] => expect((point.lat, point.lng))->toEqual((48.2082, 16.3738))
|
|
145
|
+
| _ => expect("round trip")->toBe("Located with its point intact")
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
// A row written before the field existed. Replay is what a read model is for,
|
|
150
|
+
// so this is a convenience rather than a contract — but it must not throw,
|
|
151
|
+
// because a single unparseable row otherwise fails the whole page.
|
|
152
|
+
testPromise("a row written before the union field arrives as the first arm", async () => {
|
|
153
|
+
store.contents->Dict.set(
|
|
154
|
+
"c-7",
|
|
155
|
+
[JSON.Encode.object(Dict.fromArray([("customerId", JSON.Encode.string("c-7"))]))],
|
|
156
|
+
)
|
|
157
|
+
let result = await Customers.loadStream("c-7")->Stream.runCollect->Effect.runPromise
|
|
158
|
+
switch result {
|
|
159
|
+
| [{geolocation: Pending({requestedFor})}] => expect(requestedFor)->toBe("")
|
|
160
|
+
| _ => expect("healed row")->toBe("Pending")
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
})
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stream from "@reventlessdev/rescript-effect/src/Stream.res.mjs";
|
|
4
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
5
|
+
import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
|
|
6
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as QueryDbFixtures$ReventlessCore from "./QueryDbFixtures.res.mjs";
|
|
9
|
+
import * as QueryDb_Operations$ReventlessCore from "../../src/components/QueryDb/QueryDb_Operations.res.mjs";
|
|
10
|
+
import * as TaggedUnionFixtures$ReventlessCore from "../fixtures/TaggedUnionFixtures.res.mjs";
|
|
11
|
+
|
|
12
|
+
let Customers = QueryDb_Operations$ReventlessCore.Make({
|
|
13
|
+
Id: {
|
|
14
|
+
schema: Id$Reventless.StringPure.schema,
|
|
15
|
+
make: prim => prim,
|
|
16
|
+
makeFromString: prim => prim,
|
|
17
|
+
toString: prim => prim,
|
|
18
|
+
cmp: Id$Reventless.StringPure.cmp
|
|
19
|
+
},
|
|
20
|
+
name: TaggedUnionFixtures$ReventlessCore.CustomersSpec.name,
|
|
21
|
+
moduleUrl: TaggedUnionFixtures$ReventlessCore.CustomersSpec.moduleUrl,
|
|
22
|
+
stateSchema: TaggedUnionFixtures$ReventlessCore.CustomersSpec.stateSchema,
|
|
23
|
+
config: TaggedUnionFixtures$ReventlessCore.CustomersSpec.config,
|
|
24
|
+
subIdConfig: TaggedUnionFixtures$ReventlessCore.CustomersSpec.subIdConfig,
|
|
25
|
+
authorization: TaggedUnionFixtures$ReventlessCore.CustomersSpec.authorization,
|
|
26
|
+
visibility: TaggedUnionFixtures$ReventlessCore.CustomersSpec.visibility
|
|
27
|
+
})({
|
|
28
|
+
jsonOps: QueryDbFixtures$ReventlessCore.mockJsonOps
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
let Sightings = QueryDb_Operations$ReventlessCore.Make({
|
|
32
|
+
Id: {
|
|
33
|
+
schema: Id$Reventless.StringPure.schema,
|
|
34
|
+
make: prim => prim,
|
|
35
|
+
makeFromString: prim => prim,
|
|
36
|
+
toString: prim => prim,
|
|
37
|
+
cmp: Id$Reventless.StringPure.cmp
|
|
38
|
+
},
|
|
39
|
+
name: TaggedUnionFixtures$ReventlessCore.SightingsSpec.name,
|
|
40
|
+
moduleUrl: TaggedUnionFixtures$ReventlessCore.SightingsSpec.moduleUrl,
|
|
41
|
+
stateSchema: TaggedUnionFixtures$ReventlessCore.SightingsSpec.stateSchema,
|
|
42
|
+
config: TaggedUnionFixtures$ReventlessCore.SightingsSpec.config,
|
|
43
|
+
subIdConfig: TaggedUnionFixtures$ReventlessCore.SightingsSpec.subIdConfig,
|
|
44
|
+
authorization: TaggedUnionFixtures$ReventlessCore.SightingsSpec.authorization,
|
|
45
|
+
visibility: TaggedUnionFixtures$ReventlessCore.SightingsSpec.visibility
|
|
46
|
+
})({
|
|
47
|
+
jsonOps: QueryDbFixtures$ReventlessCore.mockJsonOps
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
let Items = QueryDb_Operations$ReventlessCore.Make({
|
|
51
|
+
Id: {
|
|
52
|
+
schema: Id$Reventless.StringPure.schema,
|
|
53
|
+
make: prim => prim,
|
|
54
|
+
makeFromString: prim => prim,
|
|
55
|
+
toString: prim => prim,
|
|
56
|
+
cmp: Id$Reventless.StringPure.cmp
|
|
57
|
+
},
|
|
58
|
+
name: QueryDbFixtures$ReventlessCore.ItemQueryDbSpec.name,
|
|
59
|
+
moduleUrl: QueryDbFixtures$ReventlessCore.ItemQueryDbSpec.moduleUrl,
|
|
60
|
+
stateSchema: QueryDbFixtures$ReventlessCore.ItemQueryDbSpec.stateSchema,
|
|
61
|
+
config: QueryDbFixtures$ReventlessCore.ItemQueryDbSpec.config,
|
|
62
|
+
subIdConfig: QueryDbFixtures$ReventlessCore.ItemQueryDbSpec.subIdConfig,
|
|
63
|
+
authorization: QueryDbFixtures$ReventlessCore.ItemQueryDbSpec.authorization,
|
|
64
|
+
visibility: QueryDbFixtures$ReventlessCore.ItemQueryDbSpec.visibility
|
|
65
|
+
})({
|
|
66
|
+
jsonOps: QueryDbFixtures$ReventlessCore.mockJsonOps
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
globalThis.beforeEach(() => QueryDbFixtures$ReventlessCore.reset());
|
|
70
|
+
|
|
71
|
+
function storedRow(id) {
|
|
72
|
+
return Stdlib_Option.getOr(Stdlib_Option.getOr(QueryDbFixtures$ReventlessCore.store.contents[id], [])[0], null);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function field(json, name) {
|
|
76
|
+
return Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(json), o => o[name]), null);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function string(json) {
|
|
80
|
+
return Stdlib_JSON.Decode.string(json);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
globalThis.describe("QueryDb_Operations stamps union members", () => {
|
|
84
|
+
globalThis.test("the stored value carries __typename beside TAG", async () => {
|
|
85
|
+
await Customers.save("c-1", {
|
|
86
|
+
customerId: "c-1",
|
|
87
|
+
geolocation: {
|
|
88
|
+
TAG: "Located",
|
|
89
|
+
point: {
|
|
90
|
+
lat: 48.2082,
|
|
91
|
+
lng: 16.3738
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}, "Init", undefined);
|
|
95
|
+
let geo = field(storedRow("c-1"), "geolocation");
|
|
96
|
+
globalThis.expect(Stdlib_JSON.Decode.string(field(geo, "TAG"))).toEqual("Located");
|
|
97
|
+
globalThis.expect(Stdlib_JSON.Decode.string(field(geo, "__typename"))).toEqual("GeolocationLocated");
|
|
98
|
+
});
|
|
99
|
+
globalThis.test("every arm is stamped as the member type it is", async () => {
|
|
100
|
+
await Customers.save("c-2", {
|
|
101
|
+
customerId: "c-2",
|
|
102
|
+
geolocation: {
|
|
103
|
+
TAG: "Pending",
|
|
104
|
+
requestedFor: "1 High St"
|
|
105
|
+
}
|
|
106
|
+
}, "Init", undefined);
|
|
107
|
+
await Customers.save("c-3", {
|
|
108
|
+
customerId: "c-3",
|
|
109
|
+
geolocation: {
|
|
110
|
+
TAG: "Unresolvable",
|
|
111
|
+
reason: "ambiguous"
|
|
112
|
+
}
|
|
113
|
+
}, "Init", undefined);
|
|
114
|
+
globalThis.expect(Stdlib_JSON.Decode.string(field(field(storedRow("c-2"), "geolocation"), "__typename"))).toEqual("GeolocationPending");
|
|
115
|
+
globalThis.expect(Stdlib_JSON.Decode.string(field(field(storedRow("c-3"), "geolocation"), "__typename"))).toEqual("GeolocationUnresolvable");
|
|
116
|
+
});
|
|
117
|
+
globalThis.test("an optional union and an array of them are stamped too", async () => {
|
|
118
|
+
let state_lastSeen = {
|
|
119
|
+
TAG: "Located",
|
|
120
|
+
point: {
|
|
121
|
+
lat: 1.0,
|
|
122
|
+
lng: 2.0
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
let state_history = [
|
|
126
|
+
{
|
|
127
|
+
TAG: "Pending",
|
|
128
|
+
requestedFor: "somewhere"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
TAG: "Unresolvable",
|
|
132
|
+
reason: "gone"
|
|
133
|
+
}
|
|
134
|
+
];
|
|
135
|
+
let state = {
|
|
136
|
+
sightingId: "s-1",
|
|
137
|
+
lastSeen: state_lastSeen,
|
|
138
|
+
history: state_history
|
|
139
|
+
};
|
|
140
|
+
await Sightings.save("s-1", state, "Init", undefined);
|
|
141
|
+
let row = storedRow("s-1");
|
|
142
|
+
globalThis.expect(Stdlib_JSON.Decode.string(field(field(row, "lastSeen"), "__typename"))).toEqual("GeolocationLocated");
|
|
143
|
+
let history = Stdlib_Option.getOr(Stdlib_JSON.Decode.array(field(row, "history")), []);
|
|
144
|
+
globalThis.expect(history.map(h => Stdlib_JSON.Decode.string(field(h, "__typename")))).toEqual([
|
|
145
|
+
"GeolocationPending",
|
|
146
|
+
"GeolocationUnresolvable"
|
|
147
|
+
]);
|
|
148
|
+
});
|
|
149
|
+
globalThis.test("an absent optional union leaves nothing behind", async () => {
|
|
150
|
+
await Sightings.save("s-2", {
|
|
151
|
+
sightingId: "s-2",
|
|
152
|
+
lastSeen: undefined,
|
|
153
|
+
history: []
|
|
154
|
+
}, "Init", undefined);
|
|
155
|
+
globalThis.expect(field(storedRow("s-2"), "lastSeen")).toEqual(null);
|
|
156
|
+
});
|
|
157
|
+
globalThis.test("a view with no union field stores what it always stored", async () => {
|
|
158
|
+
await Items.save("item-1", {
|
|
159
|
+
name: "Widget",
|
|
160
|
+
count: 5
|
|
161
|
+
}, "Init", undefined);
|
|
162
|
+
globalThis.expect(JSON.stringify(storedRow("item-1"))).toBe(`{"name":"Widget","count":5,"id":"item-1"}`);
|
|
163
|
+
});
|
|
164
|
+
globalThis.test("saveBatch stamps every row", async () => {
|
|
165
|
+
let batch = [
|
|
166
|
+
[
|
|
167
|
+
"c-4",
|
|
168
|
+
{
|
|
169
|
+
customerId: "c-4",
|
|
170
|
+
geolocation: {
|
|
171
|
+
TAG: "Located",
|
|
172
|
+
point: {
|
|
173
|
+
lat: 0.0,
|
|
174
|
+
lng: 0.0
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
undefined
|
|
179
|
+
],
|
|
180
|
+
[
|
|
181
|
+
"c-5",
|
|
182
|
+
{
|
|
183
|
+
customerId: "c-5",
|
|
184
|
+
geolocation: {
|
|
185
|
+
TAG: "Pending",
|
|
186
|
+
requestedFor: "x"
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
undefined
|
|
190
|
+
]
|
|
191
|
+
];
|
|
192
|
+
await Customers.saveBatch(batch);
|
|
193
|
+
globalThis.expect([
|
|
194
|
+
Stdlib_JSON.Decode.string(field(field(storedRow("c-4"), "geolocation"), "__typename")),
|
|
195
|
+
Stdlib_JSON.Decode.string(field(field(storedRow("c-5"), "geolocation"), "__typename"))
|
|
196
|
+
]).toEqual([
|
|
197
|
+
"GeolocationLocated",
|
|
198
|
+
"GeolocationPending"
|
|
199
|
+
]);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
globalThis.describe("a stamped row decodes back", () => {
|
|
204
|
+
globalThis.test("the extra key does not disturb the parse", async () => {
|
|
205
|
+
await Customers.save("c-6", {
|
|
206
|
+
customerId: "c-6",
|
|
207
|
+
geolocation: {
|
|
208
|
+
TAG: "Located",
|
|
209
|
+
point: {
|
|
210
|
+
lat: 48.2082,
|
|
211
|
+
lng: 16.3738
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}, "Init", undefined);
|
|
215
|
+
let result = await Effect.runPromise(Stream.runCollect(Customers.loadStream("c-6")));
|
|
216
|
+
if (result.length === 1) {
|
|
217
|
+
let match = result[0];
|
|
218
|
+
let match$1 = match.geolocation;
|
|
219
|
+
switch (match$1.TAG) {
|
|
220
|
+
case "Located" :
|
|
221
|
+
let point = match$1.point;
|
|
222
|
+
globalThis.expect([
|
|
223
|
+
point.lat,
|
|
224
|
+
point.lng
|
|
225
|
+
]).toEqual([
|
|
226
|
+
48.2082,
|
|
227
|
+
16.3738
|
|
228
|
+
]);
|
|
229
|
+
return;
|
|
230
|
+
case "Pending" :
|
|
231
|
+
case "Unresolvable" :
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
globalThis.expect("round trip").toBe("Located with its point intact");
|
|
236
|
+
});
|
|
237
|
+
globalThis.test("a row written before the union field arrives as the first arm", async () => {
|
|
238
|
+
QueryDbFixtures$ReventlessCore.store.contents["c-7"] = [Object.fromEntries([[
|
|
239
|
+
"customerId",
|
|
240
|
+
"c-7"
|
|
241
|
+
]])];
|
|
242
|
+
let result = await Effect.runPromise(Stream.runCollect(Customers.loadStream("c-7")));
|
|
243
|
+
if (result.length === 1) {
|
|
244
|
+
let match = result[0];
|
|
245
|
+
let match$1 = match.geolocation;
|
|
246
|
+
switch (match$1.TAG) {
|
|
247
|
+
case "Pending" :
|
|
248
|
+
globalThis.expect(match$1.requestedFor).toBe("");
|
|
249
|
+
return;
|
|
250
|
+
case "Located" :
|
|
251
|
+
case "Unresolvable" :
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
globalThis.expect("healed row").toBe("Pending");
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
export {
|
|
260
|
+
Customers,
|
|
261
|
+
Sightings,
|
|
262
|
+
Items,
|
|
263
|
+
storedRow,
|
|
264
|
+
field,
|
|
265
|
+
string,
|
|
266
|
+
}
|
|
267
|
+
/* Customers Not a pure module */
|