@reventlessdev/reventless-core 3.0.0-alpha.211 → 3.0.0-alpha.213
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 +22 -0
- package/package.json +7 -7
- package/src/Projection.res +5 -2
- package/src/Projection.res.mjs +2 -1
- package/src/admin/Platform_AdminApi.res +2 -0
- package/src/admin/Platform_AdminApi.res.mjs +3 -1
- package/src/admin/Platform_ComponentDefinitionsApi.res +26 -5
- package/src/admin/Platform_ComponentDefinitionsApi.res.mjs +21 -5
- package/src/admin/Platform_PluginStructuresApi.res +132 -0
- package/src/admin/Platform_PluginStructuresApi.res.mjs +117 -0
- package/src/components/AutomationSlice/AutomationSlice_Callback.res +30 -0
- package/src/components/AutomationSlice/AutomationSlice_Callback.res.mjs +12 -0
- package/src/components/Dcb/Dcb_Builder.res +6 -2
- package/src/components/Dcb/Dcb_Builder.res.mjs +2 -2
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Callback.res +32 -0
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Callback.res.mjs +13 -0
- package/src/plugin/component/Plugin_Builder.res +6 -13
- package/src/plugin/component/Plugin_Builder.res.mjs +2 -16
- package/src/plugin/component/Plugin_Helpers.res +21 -3
- package/src/plugin/component/Plugin_Helpers.res.mjs +16 -0
- package/src/util/LogFormat.res +52 -1
- package/src/util/LogFormat.res.mjs +21 -2
- package/src/util/StoreLayout.res +38 -0
- package/src/util/StoreLayout.res.mjs +22 -0
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res +12 -7
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res.mjs +2 -2
- package/tests/admin/Platform_PluginStructuresApiTest.res +144 -0
- package/tests/admin/Platform_PluginStructuresApiTest.res.mjs +197 -0
- package/tests/api/GraphQL_FragmentGeneratorTest.res +40 -0
- package/tests/api/GraphQL_FragmentGeneratorTest.res.mjs +35 -0
- package/tests/logger/LogFormatTest.res +42 -0
- package/tests/logger/LogFormatTest.res.mjs +37 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
4
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
5
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
|
+
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
7
|
+
import * as Platform_PluginStructuresApi$ReventlessCore from "../../src/admin/Platform_PluginStructuresApi.res.mjs";
|
|
8
|
+
|
|
9
|
+
let publicRm_consumedEventTypes = ["ProductAdded"];
|
|
10
|
+
|
|
11
|
+
let publicRm_linkedWriteSide = ["Product"];
|
|
12
|
+
|
|
13
|
+
let publicRm_searchableFields = ["displayName"];
|
|
14
|
+
|
|
15
|
+
let publicRm_labelFieldSource = "annotation";
|
|
16
|
+
|
|
17
|
+
let publicRm_chapter = "Inventory";
|
|
18
|
+
|
|
19
|
+
let publicRm = {
|
|
20
|
+
name: "Products",
|
|
21
|
+
queryField: "Catalog_Products",
|
|
22
|
+
schema: "{}",
|
|
23
|
+
consumedEventTypes: publicRm_consumedEventTypes,
|
|
24
|
+
linkedWriteSide: publicRm_linkedWriteSide,
|
|
25
|
+
labelField: "displayName",
|
|
26
|
+
searchableFields: publicRm_searchableFields,
|
|
27
|
+
labelFieldSource: publicRm_labelFieldSource,
|
|
28
|
+
statusField: undefined,
|
|
29
|
+
visibility: undefined,
|
|
30
|
+
chapter: publicRm_chapter
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
let internalRm_consumedEventTypes = publicRm_consumedEventTypes;
|
|
34
|
+
|
|
35
|
+
let internalRm_linkedWriteSide = publicRm_linkedWriteSide;
|
|
36
|
+
|
|
37
|
+
let internalRm_searchableFields = publicRm_searchableFields;
|
|
38
|
+
|
|
39
|
+
let internalRm_labelFieldSource = "annotation";
|
|
40
|
+
|
|
41
|
+
let internalRm_visibility = "Internal";
|
|
42
|
+
|
|
43
|
+
let internalRm_chapter = "Inventory";
|
|
44
|
+
|
|
45
|
+
let internalRm = {
|
|
46
|
+
name: "ProductAudit",
|
|
47
|
+
queryField: "Catalog_ProductAudit",
|
|
48
|
+
schema: "{}",
|
|
49
|
+
consumedEventTypes: internalRm_consumedEventTypes,
|
|
50
|
+
linkedWriteSide: internalRm_linkedWriteSide,
|
|
51
|
+
labelField: "displayName",
|
|
52
|
+
searchableFields: internalRm_searchableFields,
|
|
53
|
+
labelFieldSource: internalRm_labelFieldSource,
|
|
54
|
+
statusField: undefined,
|
|
55
|
+
visibility: internalRm_visibility,
|
|
56
|
+
chapter: internalRm_chapter
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
let structure_readModels = [
|
|
60
|
+
publicRm,
|
|
61
|
+
internalRm
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
let structure_stateViewSlices = [internalRm];
|
|
65
|
+
|
|
66
|
+
let structure_stateChangeSlices = [];
|
|
67
|
+
|
|
68
|
+
let structure_aggregates = [];
|
|
69
|
+
|
|
70
|
+
let structure_automationSlices = [];
|
|
71
|
+
|
|
72
|
+
let structure_outboundTranslationSlices = [];
|
|
73
|
+
|
|
74
|
+
let structure_inboundTranslationSlices = [];
|
|
75
|
+
|
|
76
|
+
let structure_extensions = [];
|
|
77
|
+
|
|
78
|
+
let structure_extensionPoints = [{
|
|
79
|
+
name: "Catalog.Products",
|
|
80
|
+
delegateNames: ["onAdded"],
|
|
81
|
+
sourceEventTypes: ["Catalog.ProductAdded"],
|
|
82
|
+
commandTypes: undefined
|
|
83
|
+
}];
|
|
84
|
+
|
|
85
|
+
let structure_requiredStores = ["Catalog.images"];
|
|
86
|
+
|
|
87
|
+
let structure_requiredStoreDeclarations = [{
|
|
88
|
+
store: "Catalog.images",
|
|
89
|
+
component: "Product",
|
|
90
|
+
field: "image",
|
|
91
|
+
annotation: "images"
|
|
92
|
+
}];
|
|
93
|
+
|
|
94
|
+
let structure = {
|
|
95
|
+
readModels: structure_readModels,
|
|
96
|
+
stateViewSlices: structure_stateViewSlices,
|
|
97
|
+
stateChangeSlices: structure_stateChangeSlices,
|
|
98
|
+
aggregates: structure_aggregates,
|
|
99
|
+
automationSlices: structure_automationSlices,
|
|
100
|
+
outboundTranslationSlices: structure_outboundTranslationSlices,
|
|
101
|
+
inboundTranslationSlices: structure_inboundTranslationSlices,
|
|
102
|
+
extensions: structure_extensions,
|
|
103
|
+
extensionPoints: structure_extensionPoints,
|
|
104
|
+
requiredStores: structure_requiredStores,
|
|
105
|
+
requiredStoreDeclarations: structure_requiredStoreDeclarations
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
let encoded = Platform_PluginStructuresApi$ReventlessCore.encodePluginStructureEntry("Catalog", structure);
|
|
109
|
+
|
|
110
|
+
let json = JSON.stringify(encoded);
|
|
111
|
+
|
|
112
|
+
function namesOf(key) {
|
|
113
|
+
return Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(encoded), d => d[key]), Stdlib_JSON.Decode.array), []), v => Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(v), o => o["name"]), Stdlib_JSON.Decode.string));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
globalThis.describe("Platform_PluginStructures entry", () => {
|
|
117
|
+
globalThis.test("carries the structure-level keys the AutoUI entry omits", () => {
|
|
118
|
+
let dict = Stdlib_Option.getOr(Stdlib_JSON.Decode.object(encoded), {});
|
|
119
|
+
let keys = Object.keys(dict).toSorted(Primitive_string.compare);
|
|
120
|
+
globalThis.expect(keys).toEqual([
|
|
121
|
+
"aggregates",
|
|
122
|
+
"automationSlices",
|
|
123
|
+
"extensionPoints",
|
|
124
|
+
"extensions",
|
|
125
|
+
"inboundTranslationSlices",
|
|
126
|
+
"outboundTranslationSlices",
|
|
127
|
+
"pluginId",
|
|
128
|
+
"readModels",
|
|
129
|
+
"requiredStoreDeclarations",
|
|
130
|
+
"requiredStores",
|
|
131
|
+
"stateChangeSlices",
|
|
132
|
+
"stateViewSlices"
|
|
133
|
+
]);
|
|
134
|
+
});
|
|
135
|
+
globalThis.test("keeps Internal read models", () => {
|
|
136
|
+
globalThis.expect(namesOf("readModels")).toEqual([
|
|
137
|
+
"Products",
|
|
138
|
+
"ProductAudit"
|
|
139
|
+
]);
|
|
140
|
+
});
|
|
141
|
+
globalThis.test("keeps Internal state-view slices", () => {
|
|
142
|
+
globalThis.expect(namesOf("stateViewSlices")).toEqual(["ProductAudit"]);
|
|
143
|
+
});
|
|
144
|
+
globalThis.test("carries visibility so a consumer can tell which are Internal", () => {
|
|
145
|
+
globalThis.expect(json.includes("\"visibility\":\"Internal\"")).toEqual(true);
|
|
146
|
+
});
|
|
147
|
+
globalThis.test("encodes the chapter band", () => {
|
|
148
|
+
globalThis.expect(json.includes("\"chapter\":\"Inventory\"")).toEqual(true);
|
|
149
|
+
});
|
|
150
|
+
globalThis.test("encodes extension points with their source event types", () => {
|
|
151
|
+
globalThis.expect(json.includes("\"extensionPoints\":[{\"name\":\"Catalog.Products\",\"delegateNames\":[\"onAdded\"],\"sourceEventTypes\":[\"Catalog.ProductAdded\"],\"commandTypes\":null}]")).toEqual(true);
|
|
152
|
+
});
|
|
153
|
+
globalThis.test("encodes required stores with their provenance", () => {
|
|
154
|
+
globalThis.expect(json.includes("\"requiredStores\":[\"Catalog.images\"]")).toEqual(true);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
globalThis.describe("absent optional collections", () => {
|
|
159
|
+
let bare_readModels = structure_readModels;
|
|
160
|
+
let bare_stateViewSlices = structure_stateViewSlices;
|
|
161
|
+
let bare_stateChangeSlices = structure_stateChangeSlices;
|
|
162
|
+
let bare_aggregates = structure_aggregates;
|
|
163
|
+
let bare_automationSlices = structure_automationSlices;
|
|
164
|
+
let bare_outboundTranslationSlices = structure_outboundTranslationSlices;
|
|
165
|
+
let bare_inboundTranslationSlices = structure_inboundTranslationSlices;
|
|
166
|
+
let bare_extensions = structure_extensions;
|
|
167
|
+
let bare = {
|
|
168
|
+
readModels: bare_readModels,
|
|
169
|
+
stateViewSlices: bare_stateViewSlices,
|
|
170
|
+
stateChangeSlices: bare_stateChangeSlices,
|
|
171
|
+
aggregates: bare_aggregates,
|
|
172
|
+
automationSlices: bare_automationSlices,
|
|
173
|
+
outboundTranslationSlices: bare_outboundTranslationSlices,
|
|
174
|
+
inboundTranslationSlices: bare_inboundTranslationSlices,
|
|
175
|
+
extensions: bare_extensions,
|
|
176
|
+
extensionPoints: undefined,
|
|
177
|
+
requiredStores: undefined,
|
|
178
|
+
requiredStoreDeclarations: undefined
|
|
179
|
+
};
|
|
180
|
+
let bareJson = JSON.stringify(Platform_PluginStructuresApi$ReventlessCore.encodePluginStructureEntry("Catalog", bare));
|
|
181
|
+
globalThis.test("encodes absent extensionPoints as null", () => {
|
|
182
|
+
globalThis.expect(bareJson.includes("\"extensionPoints\":null")).toEqual(true);
|
|
183
|
+
});
|
|
184
|
+
globalThis.test("encodes absent requiredStores as null", () => {
|
|
185
|
+
globalThis.expect(bareJson.includes("\"requiredStores\":null")).toEqual(true);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
export {
|
|
190
|
+
publicRm,
|
|
191
|
+
internalRm,
|
|
192
|
+
structure,
|
|
193
|
+
encoded,
|
|
194
|
+
json,
|
|
195
|
+
namesOf,
|
|
196
|
+
}
|
|
197
|
+
/* encoded Not a pure module */
|
|
@@ -47,3 +47,43 @@ describe("GraphQL_FragmentGenerator.generate mutation id injection", () => {
|
|
|
47
47
|
expect(field->String.includes("id: ID!, orderId"))->toBe(true)
|
|
48
48
|
})
|
|
49
49
|
})
|
|
50
|
+
|
|
51
|
+
// A slice TODO row stores its work item as `JSON.t`, which the IR can only call
|
|
52
|
+
// `Unknown` — and `Unknown` renders as `String!`. Serving a stored object
|
|
53
|
+
// through a String field fails at execution ("String cannot represent value:
|
|
54
|
+
// { … }") as soon as a client selects it, so `Dcb_Builder` describes the row
|
|
55
|
+
// with `todoRowSchemaFor` and the item's own schema instead.
|
|
56
|
+
module GeocodeItem = {
|
|
57
|
+
@schema
|
|
58
|
+
type outboundItem = {customerId: string, address: string}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let typeDefFor = (fragment, needle) =>
|
|
62
|
+
GraphQL_Stitcher.decode(fragment).types->Array.find(t => t->String.includes(needle))
|
|
63
|
+
|
|
64
|
+
describe("GraphQL_FragmentGenerator slice TODO rows", () => {
|
|
65
|
+
testSync("item carries the slice's item type, not String", () => {
|
|
66
|
+
let fragment = GraphQL_FragmentGenerator.generate(
|
|
67
|
+
~mutationEntries=[],
|
|
68
|
+
~queryEntries=[
|
|
69
|
+
{
|
|
70
|
+
ReventlessInfra.Api.singleFieldName: "Ordering_GeocodeCustomerAddressTodo",
|
|
71
|
+
listFieldName: "Ordering_GeocodeCustomerAddressTodos",
|
|
72
|
+
returnTypeName: "Ordering_GeocodeCustomerAddressTodo",
|
|
73
|
+
stateSchema: OutboundTranslationSlice_Callback.todoRowSchemaFor(
|
|
74
|
+
GeocodeItem.outboundItemSchema,
|
|
75
|
+
)->S.castToUnknown,
|
|
76
|
+
authorization: None,
|
|
77
|
+
connectionSpec: true,
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
)
|
|
81
|
+
let row = typeDefFor(fragment, "type Ordering_GeocodeCustomerAddressTodo ")->Option.getOr("")
|
|
82
|
+
expect(row->String.includes("item: Ordering_GeocodeCustomerAddressTodoItem!"))->toBe(true)
|
|
83
|
+
|
|
84
|
+
let item =
|
|
85
|
+
typeDefFor(fragment, "type Ordering_GeocodeCustomerAddressTodoItem ")->Option.getOr("")
|
|
86
|
+
expect(item->String.includes("customerId: ID!"))->toBe(true)
|
|
87
|
+
expect(item->String.includes("address: String!"))->toBe(true)
|
|
88
|
+
})
|
|
89
|
+
})
|
|
@@ -4,6 +4,7 @@ import * as S from "sury/src/S.res.mjs";
|
|
|
4
4
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
5
|
import * as GraphQL_Stitcher$ReventlessCore from "../../src/components/Api/GraphQL_Stitcher.res.mjs";
|
|
6
6
|
import * as GraphQL_FragmentGenerator$ReventlessCore from "../../src/components/Api/GraphQL_FragmentGenerator.res.mjs";
|
|
7
|
+
import * as OutboundTranslationSlice_Callback$ReventlessCore from "../../src/components/OutboundTranslationSlice/OutboundTranslationSlice_Callback.res.mjs";
|
|
7
8
|
|
|
8
9
|
let commandSchema = S.union([
|
|
9
10
|
S.schema(s => ({
|
|
@@ -50,8 +51,42 @@ globalThis.describe("GraphQL_FragmentGenerator.generate mutation id injection",
|
|
|
50
51
|
});
|
|
51
52
|
});
|
|
52
53
|
|
|
54
|
+
let outboundItemSchema = S.schema(s => ({
|
|
55
|
+
customerId: s.m(S.string),
|
|
56
|
+
address: s.m(S.string)
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
let GeocodeItem = {
|
|
60
|
+
outboundItemSchema: outboundItemSchema,
|
|
61
|
+
externalSystem: undefined
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function typeDefFor(fragment, needle) {
|
|
65
|
+
return GraphQL_Stitcher$ReventlessCore.decode(fragment).types.find(t => t.includes(needle));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
globalThis.describe("GraphQL_FragmentGenerator slice TODO rows", () => {
|
|
69
|
+
globalThis.test("item carries the slice's item type, not String", () => {
|
|
70
|
+
let fragment = GraphQL_FragmentGenerator$ReventlessCore.generate([], [{
|
|
71
|
+
singleFieldName: "Ordering_GeocodeCustomerAddressTodo",
|
|
72
|
+
listFieldName: "Ordering_GeocodeCustomerAddressTodos",
|
|
73
|
+
returnTypeName: "Ordering_GeocodeCustomerAddressTodo",
|
|
74
|
+
stateSchema: OutboundTranslationSlice_Callback$ReventlessCore.todoRowSchemaFor(outboundItemSchema),
|
|
75
|
+
authorization: undefined,
|
|
76
|
+
connectionSpec: true
|
|
77
|
+
}]);
|
|
78
|
+
let row = Stdlib_Option.getOr(typeDefFor(fragment, "type Ordering_GeocodeCustomerAddressTodo "), "");
|
|
79
|
+
globalThis.expect(row.includes("item: Ordering_GeocodeCustomerAddressTodoItem!")).toBe(true);
|
|
80
|
+
let item = Stdlib_Option.getOr(typeDefFor(fragment, "type Ordering_GeocodeCustomerAddressTodoItem "), "");
|
|
81
|
+
globalThis.expect(item.includes("customerId: ID!")).toBe(true);
|
|
82
|
+
globalThis.expect(item.includes("address: String!")).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
53
86
|
export {
|
|
54
87
|
OrderSliceCmd,
|
|
55
88
|
mutationFor,
|
|
89
|
+
GeocodeItem,
|
|
90
|
+
typeDefFor,
|
|
56
91
|
}
|
|
57
92
|
/* commandSchema Not a pure module */
|
|
@@ -127,6 +127,48 @@ describe("LogFormat", () => {
|
|
|
127
127
|
},
|
|
128
128
|
)
|
|
129
129
|
})
|
|
130
|
+
|
|
131
|
+
// A ReScript record's absent optional field survives encoding as an own
|
|
132
|
+
// property whose value is `undefined` — the one thing `JSON.t` cannot hold and
|
|
133
|
+
// `JSON.stringify` answers with `undefined` instead of a string. Rendering a
|
|
134
|
+
// command carrying one must not reach a string function with that `undefined`:
|
|
135
|
+
// `variantFields` runs inside the CommandGenerator's log tap, so a throw there
|
|
136
|
+
// takes the command down with it and the slice never sees it.
|
|
137
|
+
describe("variantFields", () => {
|
|
138
|
+
module OptionalFieldCmd = {
|
|
139
|
+
@schema
|
|
140
|
+
type command = AddProduct({productId: string, name: string, imageUrl?: string})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
testSync("omits an absent optional field", () => {
|
|
144
|
+
let json =
|
|
145
|
+
OptionalFieldCmd.AddProduct({productId: "prd-1", name: "Cirrus"})
|
|
146
|
+
->Message.encode(OptionalFieldCmd.commandSchema)
|
|
147
|
+
expect(variantFields(json))->toEqual(`, {productId:"prd-1",name:"Cirrus"}`)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
testSync("keeps a present optional field", () => {
|
|
151
|
+
let json =
|
|
152
|
+
OptionalFieldCmd.AddProduct({productId: "prd-1", name: "Cirrus", imageUrl: "/u/a.png"})
|
|
153
|
+
->Message.encode(OptionalFieldCmd.commandSchema)
|
|
154
|
+
expect(variantFields(json))->toEqual(
|
|
155
|
+
`, {productId:"prd-1",name:"Cirrus",imageUrl:"/u/a.png"}`,
|
|
156
|
+
)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
testSync("elides a value past the budget rather than dropping it", () => {
|
|
160
|
+
let long = String.repeat("x", maxValueChars + 20)
|
|
161
|
+
let json =
|
|
162
|
+
OptionalFieldCmd.AddProduct({productId: "prd-1", name: long})
|
|
163
|
+
->Message.encode(OptionalFieldCmd.commandSchema)
|
|
164
|
+
let rendered = variantFields(json)
|
|
165
|
+
expect(rendered->String.includes("…(+22 chars)"))->toBe(true)
|
|
166
|
+
// The oversized value is capped, not the line: the neighbouring field that
|
|
167
|
+
// says which product this is stays intact.
|
|
168
|
+
expect(rendered->String.includes(long))->toBe(false)
|
|
169
|
+
expect(rendered->String.includes(`productId:"prd-1"`))->toBe(true)
|
|
170
|
+
})
|
|
171
|
+
})
|
|
130
172
|
})
|
|
131
173
|
|
|
132
174
|
// ─── JSON sink (non-TTY collector: CloudWatch / Datadog / …) ─────────────────
|
|
@@ -124,6 +124,43 @@ globalThis.describe("LogFormat", () => {
|
|
|
124
124
|
globalThis.expect(msg).toEqual(`\x1b[1mVersionDetected\x1b[0m(testId): {"event":{"TAG":"VersionDetected","_0":"0.0.0"},"meta":{"service":"testService","time":"testTime","ip":"testIp","user":"testUser","msgId":"testMsgId","correlationId":"testCorrelationId"},"id":"testId"}`);
|
|
125
125
|
});
|
|
126
126
|
});
|
|
127
|
+
globalThis.describe("variantFields", () => {
|
|
128
|
+
let commandSchema = S.schema(s => ({
|
|
129
|
+
TAG: "AddProduct",
|
|
130
|
+
productId: s.m(S.string),
|
|
131
|
+
name: s.m(S.string),
|
|
132
|
+
imageUrl: s.m(S.option(S.string))
|
|
133
|
+
}));
|
|
134
|
+
globalThis.test("omits an absent optional field", () => {
|
|
135
|
+
let json = Message$ReventlessCore.encode({
|
|
136
|
+
TAG: "AddProduct",
|
|
137
|
+
productId: "prd-1",
|
|
138
|
+
name: "Cirrus"
|
|
139
|
+
}, commandSchema);
|
|
140
|
+
globalThis.expect(LogFormat$ReventlessCore.variantFields(json)).toEqual(`, {productId:"prd-1",name:"Cirrus"}`);
|
|
141
|
+
});
|
|
142
|
+
globalThis.test("keeps a present optional field", () => {
|
|
143
|
+
let json = Message$ReventlessCore.encode({
|
|
144
|
+
TAG: "AddProduct",
|
|
145
|
+
productId: "prd-1",
|
|
146
|
+
name: "Cirrus",
|
|
147
|
+
imageUrl: "/u/a.png"
|
|
148
|
+
}, commandSchema);
|
|
149
|
+
globalThis.expect(LogFormat$ReventlessCore.variantFields(json)).toEqual(`, {productId:"prd-1",name:"Cirrus",imageUrl:"/u/a.png"}`);
|
|
150
|
+
});
|
|
151
|
+
globalThis.test("elides a value past the budget rather than dropping it", () => {
|
|
152
|
+
let long = "x".repeat(LogFormat$ReventlessCore.maxValueChars + 20 | 0);
|
|
153
|
+
let json = Message$ReventlessCore.encode({
|
|
154
|
+
TAG: "AddProduct",
|
|
155
|
+
productId: "prd-1",
|
|
156
|
+
name: long
|
|
157
|
+
}, commandSchema);
|
|
158
|
+
let rendered = LogFormat$ReventlessCore.variantFields(json);
|
|
159
|
+
globalThis.expect(rendered.includes("…(+22 chars)")).toBe(true);
|
|
160
|
+
globalThis.expect(rendered.includes(long)).toBe(false);
|
|
161
|
+
globalThis.expect(rendered.includes(`productId:"prd-1"`)).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
127
164
|
});
|
|
128
165
|
|
|
129
166
|
function hasAnsi(s) {
|