@reventlessdev/reventless-core 3.0.0-alpha.199 → 3.0.0-alpha.201
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 +31 -0
- package/package.json +7 -7
- package/src/adapter/Runtime/Runtime.res +9 -1
- package/src/adapter/Runtime/Runtime.res.mjs +1 -2
- package/src/admin/Platform_Admin.res +1 -1
- package/src/admin/Platform_Admin.res.mjs +1 -0
- package/src/admin/Platform_AdminApi.res +1 -1
- package/src/components/Api/StorageRefFields.res +124 -0
- package/src/components/Api/StorageRefFields.res.mjs +88 -0
- package/src/components/Task/Task_Builder.res +75 -4
- package/src/components/Task/Task_Builder.res.mjs +33 -1
- package/src/plugin/component/Plugin_Builder.res +1 -1
- package/src/plugin/component/Plugin_Builder.res.mjs +1 -0
- package/src/plugin/component/Plugin_Helpers.res +5 -0
- package/src/plugin/component/Plugin_Structure.res +5 -1
- package/src/plugin/component/Plugin_Structure.res.mjs +2 -1
- package/src/util/StorePrefixCollision.res +96 -0
- package/src/util/StorePrefixCollision.res.mjs +55 -0
- package/tests/StorePrefixCollisionTest.res +160 -0
- package/tests/StorePrefixCollisionTest.res.mjs +174 -0
- package/tests/TaskBucketNamingTest.res +97 -0
- package/tests/TaskBucketNamingTest.res.mjs +60 -0
- package/tests/api/StorageRefFieldsTest.res +148 -0
- package/tests/api/StorageRefFieldsTest.res.mjs +152 -0
- package/tests/api/SuryToJsonSchemaTest.res +46 -0
- package/tests/api/SuryToJsonSchemaTest.res.mjs +39 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// What the claim component ever gets to see, decided here.
|
|
2
|
+
//
|
|
3
|
+
// `StorageRefFields.fromEventSchema` is the deploy-time reading of the
|
|
4
|
+
// `@storageRef` declarations that becomes the claimer's entire input: an event
|
|
5
|
+
// variant it does not produce is an event the claimer never reads a record for,
|
|
6
|
+
// and a field it does not list is a ref that never gets claimed — so the object
|
|
7
|
+
// keeps its pending tag and, on a store with expiry enabled, is eventually
|
|
8
|
+
// deleted while something still references it.
|
|
9
|
+
//
|
|
10
|
+
// Which makes the interesting assertions the negative ones: what is *absent*
|
|
11
|
+
// from this output is what silently rots.
|
|
12
|
+
|
|
13
|
+
open JestGlobals
|
|
14
|
+
|
|
15
|
+
module Fields = StorageRefFields
|
|
16
|
+
|
|
17
|
+
// An event union, shaped the way sury encodes a ReScript variant: one object per
|
|
18
|
+
// variant, discriminated by a `TAG` string constant.
|
|
19
|
+
let variant = (~tag: string, fields: array<(string, S.t<unknown>)>): S.t<unknown> =>
|
|
20
|
+
S.object(s => {
|
|
21
|
+
let _ = s.field("TAG", S.literal(tag)->S.castToUnknown)
|
|
22
|
+
fields->Array.forEach(((name, schema)) => {
|
|
23
|
+
let _ = s.field(name, schema)
|
|
24
|
+
})
|
|
25
|
+
()
|
|
26
|
+
})->S.castToUnknown
|
|
27
|
+
|
|
28
|
+
let ref_ = (~plugin: option<string>=?, ~store: string) =>
|
|
29
|
+
Reventless.StorageRef.forStore(~plugin?, ~store)->S.castToUnknown
|
|
30
|
+
|
|
31
|
+
describe("StorageRefFields.fromEventSchema", () => {
|
|
32
|
+
testSync("finds a declared single-valued ref field and qualifies its store", () => {
|
|
33
|
+
let schema = S.union([variant(~tag="ProductImageChanged", [("imageUrl", ref_(~store="productImages"))])])->S.castToUnknown
|
|
34
|
+
expect(Fields.fromEventSchema(~plugin="Catalog", schema))->toEqual([
|
|
35
|
+
{
|
|
36
|
+
Fields.eventType: "ProductImageChanged",
|
|
37
|
+
fields: [
|
|
38
|
+
{Fields.field: "imageUrl", arity: Single, store: "Catalog.productImages"},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
])
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// The annotation's own qualification wins: a field pointing at another
|
|
45
|
+
// plugin's store must not be re-attributed to the plugin that wrote it, or the
|
|
46
|
+
// claimer resolves the wrong bucket and refuses the ref as out-of-store.
|
|
47
|
+
testSync("a qualified declaration keeps the store it names", () => {
|
|
48
|
+
let schema =
|
|
49
|
+
S.union([
|
|
50
|
+
variant(~tag="OrderPhotoAttached", [("photo", ref_(~plugin="Catalog", ~store="productImages"))]),
|
|
51
|
+
])->S.castToUnknown
|
|
52
|
+
expect(
|
|
53
|
+
Fields.fromEventSchema(~plugin="Ordering", schema)
|
|
54
|
+
->Array.flatMap(e => e.fields->Array.map(f => f.store)),
|
|
55
|
+
)->toEqual(["Catalog.productImages"])
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// `@storageRef` on an `array<string>` attaches the marker to the ELEMENT, so
|
|
59
|
+
// reading the field schema directly answers None. The claimer needs the arity
|
|
60
|
+
// to know whether to read one value or iterate.
|
|
61
|
+
testSync("finds a multi-valued ref field and records its arity", () => {
|
|
62
|
+
let schema =
|
|
63
|
+
S.union([
|
|
64
|
+
variant(~tag="GalleryReplaced", [("imageUrls", S.array(ref_(~store="productImages"))->S.castToUnknown)]),
|
|
65
|
+
])->S.castToUnknown
|
|
66
|
+
expect(
|
|
67
|
+
Fields.fromEventSchema(~plugin="Catalog", schema)->Array.flatMap(e => e.fields),
|
|
68
|
+
)->toEqual([{Fields.field: "imageUrls", arity: Multiple, store: "Catalog.productImages"}])
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// Narrow input by construction: a platform whose plugins declare no store
|
|
72
|
+
// provisions no claimer at all, and an event log carrying no declaration is
|
|
73
|
+
// never subscribed to.
|
|
74
|
+
testSync("a variant with no declared field is omitted entirely", () => {
|
|
75
|
+
let schema =
|
|
76
|
+
S.union([
|
|
77
|
+
variant(~tag="ProductRenamed", [("name", S.string->S.castToUnknown)]),
|
|
78
|
+
variant(~tag="ProductImageChanged", [("imageUrl", ref_(~store="productImages"))]),
|
|
79
|
+
])->S.castToUnknown
|
|
80
|
+
expect(Fields.fromEventSchema(~plugin="Catalog", schema)->Array.map(e => e.eventType))->toEqual([
|
|
81
|
+
"ProductImageChanged",
|
|
82
|
+
])
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
testSync("an event schema with no declaration anywhere yields nothing", () => {
|
|
86
|
+
let schema = S.union([variant(~tag="ProductRenamed", [("name", S.string->S.castToUnknown)])])->S.castToUnknown
|
|
87
|
+
expect(Fields.fromEventSchema(~plugin="Catalog", schema))->toEqual([])
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// A string field that merely looks like a ref is not one. Declaration
|
|
91
|
+
// outranks inference everywhere else in the framework, and this is the reading
|
|
92
|
+
// that has to agree with `Plugin_Structure`'s — the claimer can only reach a
|
|
93
|
+
// store the deploy actually provisioned.
|
|
94
|
+
testSync("an unannotated string field is not treated as a ref", () => {
|
|
95
|
+
let schema =
|
|
96
|
+
S.union([variant(~tag="ProductImported", [("imageUrl", S.string->S.castToUnknown)])])->S.castToUnknown
|
|
97
|
+
expect(Fields.fromEventSchema(~plugin="Catalog", schema))->toEqual([])
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
testSync("a single-variant (non-union) event schema is read the same way", () => {
|
|
101
|
+
let schema = variant(~tag="ProductImageChanged", [("imageUrl", ref_(~store="productImages"))])
|
|
102
|
+
expect(Fields.fromEventSchema(~plugin="Catalog", schema)->Array.map(e => e.eventType))->toEqual([
|
|
103
|
+
"ProductImageChanged",
|
|
104
|
+
])
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
describe("StorageRefFields.toJson", () => {
|
|
109
|
+
// The wire form crosses into a Lambda's environment, where the handler decodes
|
|
110
|
+
// it with plain JSON reads. Both ends are pinned here because a drift is
|
|
111
|
+
// silent: an unreadable entry is indistinguishable from an absent one, and an
|
|
112
|
+
// absent one means "nothing to claim".
|
|
113
|
+
testSync("emits field, arity and store per event type", () => {
|
|
114
|
+
let entries: array<Fields.eventRefFields> = [
|
|
115
|
+
{
|
|
116
|
+
eventType: "ProductImageChanged",
|
|
117
|
+
fields: [{field: "imageUrl", arity: Single, store: "Catalog.productImages"}],
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
eventType: "GalleryReplaced",
|
|
121
|
+
fields: [{field: "imageUrls", arity: Multiple, store: "Catalog.productImages"}],
|
|
122
|
+
},
|
|
123
|
+
]
|
|
124
|
+
expect(Fields.toJson(entries)->JSON.stringify)->toBe(
|
|
125
|
+
`{"ProductImageChanged":[{"field":"imageUrl","arity":"one","store":"Catalog.productImages"}],` ++
|
|
126
|
+
`"GalleryReplaced":[{"field":"imageUrls","arity":"many","store":"Catalog.productImages"}]}`,
|
|
127
|
+
)
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
describe("StorageRefFields.storesOf", () => {
|
|
132
|
+
testSync("collects the distinct stores an event log's declarations reach", () => {
|
|
133
|
+
let entries: array<Fields.eventRefFields> = [
|
|
134
|
+
{
|
|
135
|
+
eventType: "A",
|
|
136
|
+
fields: [{field: "a", arity: Single, store: "Catalog.productImages"}],
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
eventType: "B",
|
|
140
|
+
fields: [
|
|
141
|
+
{field: "b", arity: Single, store: "Catalog.productImages"},
|
|
142
|
+
{field: "c", arity: Multiple, store: "Ordering.receipts"},
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
]
|
|
146
|
+
expect(Fields.storesOf(entries))->toEqual(["Catalog.productImages", "Ordering.receipts"])
|
|
147
|
+
})
|
|
148
|
+
})
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as StorageRef$Reventless from "@reventlessdev/reventless-spec/src/semantic/StorageRef.res.mjs";
|
|
5
|
+
import * as StorageRefFields$ReventlessCore from "../../src/components/Api/StorageRefFields.res.mjs";
|
|
6
|
+
|
|
7
|
+
function variant(tag, fields) {
|
|
8
|
+
return S.object(s => {
|
|
9
|
+
s.f("TAG", S.literal(tag));
|
|
10
|
+
fields.forEach(param => {
|
|
11
|
+
s.f(param[0], param[1]);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let ref_ = StorageRef$Reventless.forStore;
|
|
17
|
+
|
|
18
|
+
globalThis.describe("StorageRefFields.fromEventSchema", () => {
|
|
19
|
+
globalThis.test("finds a declared single-valued ref field and qualifies its store", () => {
|
|
20
|
+
let schema = S.union([variant("ProductImageChanged", [[
|
|
21
|
+
"imageUrl",
|
|
22
|
+
StorageRef$Reventless.forStore(undefined, "productImages")
|
|
23
|
+
]])]);
|
|
24
|
+
globalThis.expect(StorageRefFields$ReventlessCore.fromEventSchema("Catalog", schema)).toEqual([{
|
|
25
|
+
eventType: "ProductImageChanged",
|
|
26
|
+
fields: [{
|
|
27
|
+
field: "imageUrl",
|
|
28
|
+
arity: "Single",
|
|
29
|
+
store: "Catalog.productImages"
|
|
30
|
+
}]
|
|
31
|
+
}]);
|
|
32
|
+
});
|
|
33
|
+
globalThis.test("a qualified declaration keeps the store it names", () => {
|
|
34
|
+
let schema = S.union([variant("OrderPhotoAttached", [[
|
|
35
|
+
"photo",
|
|
36
|
+
StorageRef$Reventless.forStore("Catalog", "productImages")
|
|
37
|
+
]])]);
|
|
38
|
+
globalThis.expect(StorageRefFields$ReventlessCore.fromEventSchema("Ordering", schema).flatMap(e => e.fields.map(f => f.store))).toEqual(["Catalog.productImages"]);
|
|
39
|
+
});
|
|
40
|
+
globalThis.test("finds a multi-valued ref field and records its arity", () => {
|
|
41
|
+
let schema = S.union([variant("GalleryReplaced", [[
|
|
42
|
+
"imageUrls",
|
|
43
|
+
S.array(StorageRef$Reventless.forStore(undefined, "productImages"))
|
|
44
|
+
]])]);
|
|
45
|
+
globalThis.expect(StorageRefFields$ReventlessCore.fromEventSchema("Catalog", schema).flatMap(e => e.fields)).toEqual([{
|
|
46
|
+
field: "imageUrls",
|
|
47
|
+
arity: "Multiple",
|
|
48
|
+
store: "Catalog.productImages"
|
|
49
|
+
}]);
|
|
50
|
+
});
|
|
51
|
+
globalThis.test("a variant with no declared field is omitted entirely", () => {
|
|
52
|
+
let schema = S.union([
|
|
53
|
+
variant("ProductRenamed", [[
|
|
54
|
+
"name",
|
|
55
|
+
S.string
|
|
56
|
+
]]),
|
|
57
|
+
variant("ProductImageChanged", [[
|
|
58
|
+
"imageUrl",
|
|
59
|
+
StorageRef$Reventless.forStore(undefined, "productImages")
|
|
60
|
+
]])
|
|
61
|
+
]);
|
|
62
|
+
globalThis.expect(StorageRefFields$ReventlessCore.fromEventSchema("Catalog", schema).map(e => e.eventType)).toEqual(["ProductImageChanged"]);
|
|
63
|
+
});
|
|
64
|
+
globalThis.test("an event schema with no declaration anywhere yields nothing", () => {
|
|
65
|
+
let schema = S.union([variant("ProductRenamed", [[
|
|
66
|
+
"name",
|
|
67
|
+
S.string
|
|
68
|
+
]])]);
|
|
69
|
+
globalThis.expect(StorageRefFields$ReventlessCore.fromEventSchema("Catalog", schema)).toEqual([]);
|
|
70
|
+
});
|
|
71
|
+
globalThis.test("an unannotated string field is not treated as a ref", () => {
|
|
72
|
+
let schema = S.union([variant("ProductImported", [[
|
|
73
|
+
"imageUrl",
|
|
74
|
+
S.string
|
|
75
|
+
]])]);
|
|
76
|
+
globalThis.expect(StorageRefFields$ReventlessCore.fromEventSchema("Catalog", schema)).toEqual([]);
|
|
77
|
+
});
|
|
78
|
+
globalThis.test("a single-variant (non-union) event schema is read the same way", () => {
|
|
79
|
+
let schema = variant("ProductImageChanged", [[
|
|
80
|
+
"imageUrl",
|
|
81
|
+
StorageRef$Reventless.forStore(undefined, "productImages")
|
|
82
|
+
]]);
|
|
83
|
+
globalThis.expect(StorageRefFields$ReventlessCore.fromEventSchema("Catalog", schema).map(e => e.eventType)).toEqual(["ProductImageChanged"]);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
globalThis.describe("StorageRefFields.toJson", () => {
|
|
88
|
+
globalThis.test("emits field, arity and store per event type", () => {
|
|
89
|
+
let entries = [
|
|
90
|
+
{
|
|
91
|
+
eventType: "ProductImageChanged",
|
|
92
|
+
fields: [{
|
|
93
|
+
field: "imageUrl",
|
|
94
|
+
arity: "Single",
|
|
95
|
+
store: "Catalog.productImages"
|
|
96
|
+
}]
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
eventType: "GalleryReplaced",
|
|
100
|
+
fields: [{
|
|
101
|
+
field: "imageUrls",
|
|
102
|
+
arity: "Multiple",
|
|
103
|
+
store: "Catalog.productImages"
|
|
104
|
+
}]
|
|
105
|
+
}
|
|
106
|
+
];
|
|
107
|
+
globalThis.expect(JSON.stringify(StorageRefFields$ReventlessCore.toJson(entries))).toBe(`{"ProductImageChanged":[{"field":"imageUrl","arity":"one","store":"Catalog.productImages"}],"GalleryReplaced":[{"field":"imageUrls","arity":"many","store":"Catalog.productImages"}]}`);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
globalThis.describe("StorageRefFields.storesOf", () => {
|
|
112
|
+
globalThis.test("collects the distinct stores an event log's declarations reach", () => {
|
|
113
|
+
let entries = [
|
|
114
|
+
{
|
|
115
|
+
eventType: "A",
|
|
116
|
+
fields: [{
|
|
117
|
+
field: "a",
|
|
118
|
+
arity: "Single",
|
|
119
|
+
store: "Catalog.productImages"
|
|
120
|
+
}]
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
eventType: "B",
|
|
124
|
+
fields: [
|
|
125
|
+
{
|
|
126
|
+
field: "b",
|
|
127
|
+
arity: "Single",
|
|
128
|
+
store: "Catalog.productImages"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
field: "c",
|
|
132
|
+
arity: "Multiple",
|
|
133
|
+
store: "Ordering.receipts"
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
];
|
|
138
|
+
globalThis.expect(StorageRefFields$ReventlessCore.storesOf(entries)).toEqual([
|
|
139
|
+
"Catalog.productImages",
|
|
140
|
+
"Ordering.receipts"
|
|
141
|
+
]);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
let Fields;
|
|
146
|
+
|
|
147
|
+
export {
|
|
148
|
+
Fields,
|
|
149
|
+
variant,
|
|
150
|
+
ref_,
|
|
151
|
+
}
|
|
152
|
+
/* Not a pure module */
|
|
@@ -794,4 +794,50 @@ describe("SuryToJsonSchema:", () => {
|
|
|
794
794
|
))
|
|
795
795
|
)
|
|
796
796
|
})
|
|
797
|
+
|
|
798
|
+
// The third composite. Its emission matters more than the other two's, because
|
|
799
|
+
// the reader has been resolving this shape from a *heuristic* since before the
|
|
800
|
+
// marker existed: an object with numeric `lat`/`lng` sub-properties already
|
|
801
|
+
// resolves to the geo-point semantic by shape. So the assertions below are what
|
|
802
|
+
// separate a declaration from a lucky guess — the marker and its `type` source.
|
|
803
|
+
describe("GeoPoint is an object carrying the geoPoint marker:", () => {
|
|
804
|
+
let json = SuryToJsonSchema.deriveObjectSchema(
|
|
805
|
+
S.schema(s =>
|
|
806
|
+
{
|
|
807
|
+
"location": s.matches(Reventless.GeoPoint.schema),
|
|
808
|
+
"lat": s.matches(S.float),
|
|
809
|
+
}
|
|
810
|
+
)->S.castToUnknown,
|
|
811
|
+
)
|
|
812
|
+
let keyOf = (name, key) => getPropertyOf(json, name)->Option.flatMap(s => getProperty(s, key))
|
|
813
|
+
|
|
814
|
+
testSync("the field is an object, not a pair of numbers", () =>
|
|
815
|
+
expect(keyOf("location", "type"))->toEqual(Some(JSON.Encode.string("object")))
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
testSync("it carries the geoPoint id, sourced from the type", () =>
|
|
819
|
+
expect((
|
|
820
|
+
keyOf("location", "x-reventless-semantic"),
|
|
821
|
+
keyOf("location", "x-reventless-semantic-source"),
|
|
822
|
+
))->toEqual((Some(JSON.Encode.string("geoPoint")), Some(JSON.Encode.string("type"))))
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
// The control, and the whole reason the composite exists: a bare `lat` float
|
|
826
|
+
// is exactly what the flattened pair looked like, and it emits nothing at
|
|
827
|
+
// all. Everything a map knew about it, it inferred from the name.
|
|
828
|
+
testSync("a bare lat float emits no semantic", () =>
|
|
829
|
+
expect((keyOf("lat", "type"), keyOf("lat", "x-reventless-semantic")))->toEqual((
|
|
830
|
+
Some(JSON.Encode.string("number")),
|
|
831
|
+
None,
|
|
832
|
+
))
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
testSync("both coordinates reach the wire as numbers", () => {
|
|
836
|
+
let inner = getPropertyOf(json, "location")->Option.getOr(JSON.Encode.null)
|
|
837
|
+
expect((
|
|
838
|
+
getPropertyOf(inner, "lat")->Option.flatMap(s => getProperty(s, "type")),
|
|
839
|
+
getPropertyOf(inner, "lng")->Option.flatMap(s => getProperty(s, "type")),
|
|
840
|
+
))->toEqual((Some(JSON.Encode.string("number")), Some(JSON.Encode.string("number"))))
|
|
841
|
+
})
|
|
842
|
+
})
|
|
797
843
|
})
|
|
@@ -11,6 +11,7 @@ import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/component
|
|
|
11
11
|
import * as Percent$Reventless from "@reventlessdev/reventless-spec/src/semantic/Percent.res.mjs";
|
|
12
12
|
import * as Currency$Reventless from "@reventlessdev/reventless-spec/src/semantic/Currency.res.mjs";
|
|
13
13
|
import * as DateTime$Reventless from "@reventlessdev/reventless-spec/src/types/DateTime.res.mjs";
|
|
14
|
+
import * as GeoPoint$Reventless from "@reventlessdev/reventless-spec/src/semantic/GeoPoint.res.mjs";
|
|
14
15
|
import * as DateRange$Reventless from "@reventlessdev/reventless-spec/src/semantic/DateRange.res.mjs";
|
|
15
16
|
import * as Reference$Reventless from "@reventlessdev/reventless-spec/src/components/Reference.res.mjs";
|
|
16
17
|
import * as StorageRef$Reventless from "@reventlessdev/reventless-spec/src/semantic/StorageRef.res.mjs";
|
|
@@ -1074,6 +1075,44 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
1074
1075
|
]);
|
|
1075
1076
|
});
|
|
1076
1077
|
});
|
|
1078
|
+
globalThis.describe("GeoPoint is an object carrying the geoPoint marker:", () => {
|
|
1079
|
+
let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(S.schema(s => ({
|
|
1080
|
+
location: s.m(GeoPoint$Reventless.schema),
|
|
1081
|
+
lat: s.m(S.float)
|
|
1082
|
+
})));
|
|
1083
|
+
let keyOf = (name, key) => Stdlib_Option.flatMap(getPropertyOf(json, name), s => getProperty(s, key));
|
|
1084
|
+
globalThis.test("the field is an object, not a pair of numbers", () => {
|
|
1085
|
+
globalThis.expect(keyOf("location", "type")).toEqual("object");
|
|
1086
|
+
});
|
|
1087
|
+
globalThis.test("it carries the geoPoint id, sourced from the type", () => {
|
|
1088
|
+
globalThis.expect([
|
|
1089
|
+
keyOf("location", "x-reventless-semantic"),
|
|
1090
|
+
keyOf("location", "x-reventless-semantic-source")
|
|
1091
|
+
]).toEqual([
|
|
1092
|
+
"geoPoint",
|
|
1093
|
+
"type"
|
|
1094
|
+
]);
|
|
1095
|
+
});
|
|
1096
|
+
globalThis.test("a bare lat float emits no semantic", () => {
|
|
1097
|
+
globalThis.expect([
|
|
1098
|
+
keyOf("lat", "type"),
|
|
1099
|
+
keyOf("lat", "x-reventless-semantic")
|
|
1100
|
+
]).toEqual([
|
|
1101
|
+
"number",
|
|
1102
|
+
undefined
|
|
1103
|
+
]);
|
|
1104
|
+
});
|
|
1105
|
+
globalThis.test("both coordinates reach the wire as numbers", () => {
|
|
1106
|
+
let inner = Stdlib_Option.getOr(getPropertyOf(json, "location"), null);
|
|
1107
|
+
globalThis.expect([
|
|
1108
|
+
Stdlib_Option.flatMap(getPropertyOf(inner, "lat"), s => getProperty(s, "type")),
|
|
1109
|
+
Stdlib_Option.flatMap(getPropertyOf(inner, "lng"), s => getProperty(s, "type"))
|
|
1110
|
+
]).toEqual([
|
|
1111
|
+
"number",
|
|
1112
|
+
"number"
|
|
1113
|
+
]);
|
|
1114
|
+
});
|
|
1115
|
+
});
|
|
1077
1116
|
});
|
|
1078
1117
|
|
|
1079
1118
|
export {
|