@reventlessdev/reventless-seed 1.0.0-alpha.20 → 1.0.0-alpha.21
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 +12 -0
- package/lib/bs/.compiler.log +2 -2
- package/lib/bs/ReventlessSeed.cmi +0 -0
- package/lib/bs/ReventlessSeed.cmt +0 -0
- package/lib/bs/ReventlessSeed.mlmap +1 -0
- package/lib/bs/compiler-info.json +1 -1
- package/lib/bs/src/Seed-ReventlessSeed.cmi +0 -0
- package/lib/bs/src/Seed-ReventlessSeed.cmt +0 -0
- package/lib/bs/src/Seed.ast +0 -0
- package/lib/bs/src/Seed.res +8 -2
- package/lib/bs/src/Seed_Client-ReventlessSeed.cmi +0 -0
- package/lib/bs/src/Seed_Client-ReventlessSeed.cmj +0 -0
- package/lib/bs/src/Seed_Client-ReventlessSeed.cmt +0 -0
- package/lib/bs/src/Seed_Client.ast +0 -0
- package/lib/bs/src/Seed_Client.res +169 -21
- package/lib/bs/src/Seed_Client.res.mjs +142 -7
- package/lib/bs/src/Seed_Connect-ReventlessSeed.cmi +0 -0
- package/lib/bs/src/Seed_Connect-ReventlessSeed.cmj +0 -0
- package/lib/bs/src/Seed_Connect-ReventlessSeed.cmt +0 -0
- package/lib/bs/src/Seed_Connect.ast +0 -0
- package/lib/bs/src/Seed_Connect.res +258 -19
- package/lib/bs/src/Seed_Connect.res.mjs +158 -8
- package/lib/bs/src/Seed_Prompt-ReventlessSeed.cmi +0 -0
- package/lib/bs/src/Seed_Prompt-ReventlessSeed.cmj +0 -0
- package/lib/bs/src/Seed_Prompt-ReventlessSeed.cmt +0 -0
- package/lib/bs/src/Seed_Prompt.ast +0 -0
- package/lib/bs/src/Seed_Prompt.res +47 -23
- package/lib/bs/src/Seed_Prompt.res.mjs +32 -22
- package/lib/bs/src/Seed_Random-ReventlessSeed.cmi +0 -0
- package/lib/bs/src/Seed_Random-ReventlessSeed.cmt +0 -0
- package/lib/bs/src/Seed_Random.ast +0 -0
- package/lib/bs/src/Seed_Random.res +4 -2
- package/lib/bs/src/Seed_Runner-ReventlessSeed.cmi +0 -0
- package/lib/bs/src/Seed_Runner-ReventlessSeed.cmt +0 -0
- package/lib/bs/src/Seed_Runner.ast +0 -0
- package/lib/bs/src/Seed_Runner.res +7 -12
- package/lib/bs/src/Seed_Upload-ReventlessSeed.cmi +0 -0
- package/lib/bs/src/Seed_Upload-ReventlessSeed.cmt +0 -0
- package/lib/bs/src/Seed_Upload.ast +0 -0
- package/lib/bs/src/Seed_Upload.res +4 -2
- package/lib/bs/src/Seed_Users-ReventlessSeed.cmi +0 -0
- package/lib/bs/src/Seed_Users-ReventlessSeed.cmt +0 -0
- package/lib/bs/src/Seed_Users.ast +0 -0
- package/lib/bs/src/Seed_Users.res +15 -3
- package/lib/bs/src/Seed_Users.res.mjs +2 -1
- package/lib/bs/tests/Seed_ClientTest-ReventlessSeed.cmi +0 -0
- package/lib/bs/tests/Seed_ClientTest-ReventlessSeed.cmj +0 -0
- package/lib/bs/tests/Seed_ClientTest-ReventlessSeed.cmt +0 -0
- package/lib/bs/tests/Seed_ClientTest.ast +0 -0
- package/lib/bs/tests/Seed_ClientTest.res +146 -16
- package/lib/bs/tests/Seed_ClientTest.res.mjs +102 -1
- package/lib/bs/tests/Seed_ConnectTest-ReventlessSeed.cmi +0 -0
- package/lib/bs/tests/Seed_ConnectTest-ReventlessSeed.cmj +0 -0
- package/lib/bs/tests/Seed_ConnectTest-ReventlessSeed.cmt +0 -0
- package/lib/bs/tests/Seed_ConnectTest.ast +0 -0
- package/lib/bs/tests/Seed_ConnectTest.res +64 -0
- package/lib/bs/tests/Seed_ConnectTest.res.mjs +56 -0
- package/lib/bs/tests/Seed_PromptTest-ReventlessSeed.cmi +0 -0
- package/lib/bs/tests/Seed_PromptTest-ReventlessSeed.cmt +0 -0
- package/lib/bs/tests/Seed_PromptTest.ast +0 -0
- package/lib/bs/tests/Seed_PromptTest.res +8 -12
- package/lib/bs/tests/Seed_RandomTest-ReventlessSeed.cmi +0 -0
- package/lib/bs/tests/Seed_RandomTest-ReventlessSeed.cmt +0 -0
- package/lib/bs/tests/Seed_RandomTest.ast +0 -0
- package/lib/bs/tests/Seed_RandomTest.res +130 -90
- package/lib/bs/tests/Seed_UsersTest-ReventlessSeed.cmi +0 -0
- package/lib/bs/tests/Seed_UsersTest-ReventlessSeed.cmt +0 -0
- package/lib/bs/tests/Seed_UsersTest.ast +0 -0
- package/lib/bs/tests/Seed_UsersTest.res +39 -2
- package/lib/bs/tests/Seed_UsersTest.res.mjs +20 -2
- package/lib/ocaml/.compiler.log +2 -2
- package/lib/ocaml/ReventlessSeed.cmi +0 -0
- package/lib/ocaml/ReventlessSeed.cmt +0 -0
- package/lib/ocaml/ReventlessSeed.mlmap +1 -0
- package/lib/ocaml/Seed-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed.ast +0 -0
- package/lib/ocaml/Seed.res +8 -2
- package/lib/ocaml/Seed_Client-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_Client-ReventlessSeed.cmj +0 -0
- package/lib/ocaml/Seed_Client-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_Client.ast +0 -0
- package/lib/ocaml/Seed_Client.res +169 -21
- package/lib/ocaml/Seed_ClientTest-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_ClientTest-ReventlessSeed.cmj +0 -0
- package/lib/ocaml/Seed_ClientTest-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_ClientTest.ast +0 -0
- package/lib/ocaml/Seed_ClientTest.res +146 -16
- package/lib/ocaml/Seed_Connect-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_Connect-ReventlessSeed.cmj +0 -0
- package/lib/ocaml/Seed_Connect-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_Connect.ast +0 -0
- package/lib/ocaml/Seed_Connect.res +258 -19
- package/lib/ocaml/Seed_ConnectTest-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_ConnectTest-ReventlessSeed.cmj +0 -0
- package/lib/ocaml/Seed_ConnectTest-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_ConnectTest.ast +0 -0
- package/lib/ocaml/Seed_ConnectTest.res +64 -0
- package/lib/ocaml/Seed_Prompt-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_Prompt-ReventlessSeed.cmj +0 -0
- package/lib/ocaml/Seed_Prompt-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_Prompt.ast +0 -0
- package/lib/ocaml/Seed_Prompt.res +47 -23
- package/lib/ocaml/Seed_PromptTest-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_PromptTest-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_PromptTest.ast +0 -0
- package/lib/ocaml/Seed_PromptTest.res +8 -12
- package/lib/ocaml/Seed_Random-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_Random-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_Random.ast +0 -0
- package/lib/ocaml/Seed_Random.res +4 -2
- package/lib/ocaml/Seed_RandomTest-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_RandomTest-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_RandomTest.ast +0 -0
- package/lib/ocaml/Seed_RandomTest.res +130 -90
- package/lib/ocaml/Seed_Runner-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_Runner-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_Runner.ast +0 -0
- package/lib/ocaml/Seed_Runner.res +7 -12
- package/lib/ocaml/Seed_Upload-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_Upload-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_Upload.ast +0 -0
- package/lib/ocaml/Seed_Upload.res +4 -2
- package/lib/ocaml/Seed_Users-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_Users-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_Users.ast +0 -0
- package/lib/ocaml/Seed_Users.res +15 -3
- package/lib/ocaml/Seed_UsersTest-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_UsersTest-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_UsersTest.ast +0 -0
- package/lib/ocaml/Seed_UsersTest.res +39 -2
- package/package.json +2 -2
- package/src/Seed.res +8 -2
- package/src/Seed_Client.res +169 -21
- package/src/Seed_Client.res.mjs +142 -7
- package/src/Seed_Connect.res +258 -19
- package/src/Seed_Connect.res.mjs +158 -8
- package/src/Seed_Prompt.res +47 -23
- package/src/Seed_Prompt.res.mjs +32 -22
- package/src/Seed_Random.res +4 -2
- package/src/Seed_Runner.res +7 -12
- package/src/Seed_Upload.res +4 -2
- package/src/Seed_Users.res +15 -3
- package/src/Seed_Users.res.mjs +2 -1
- package/tests/Seed_ClientTest.res +146 -16
- package/tests/Seed_ClientTest.res.mjs +102 -1
- package/tests/Seed_ConnectTest.res +64 -0
- package/tests/Seed_ConnectTest.res.mjs +56 -0
- package/tests/Seed_PromptTest.res +8 -12
- package/tests/Seed_RandomTest.res +130 -90
- package/tests/Seed_UsersTest.res +39 -2
- package/tests/Seed_UsersTest.res.mjs +20 -2
|
@@ -16,36 +16,45 @@ describe("Seed_Random:", () => {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
describe("reproducibility — the reason this module exists:", () => {
|
|
19
|
-
testSync(
|
|
20
|
-
|
|
19
|
+
testSync(
|
|
20
|
+
"the same seed replays the same sequence",
|
|
21
|
+
() => expect(take(~seed=42, ~n=25))->toEqual(take(~seed=42, ~n=25)),
|
|
21
22
|
)
|
|
22
23
|
|
|
23
24
|
// Without this, a generator that returned a constant would satisfy the
|
|
24
25
|
// case above and nothing else in the suite would catch it.
|
|
25
|
-
testSync(
|
|
26
|
-
|
|
26
|
+
testSync(
|
|
27
|
+
"different seeds diverge",
|
|
28
|
+
() => expect(take(~seed=42, ~n=25) == take(~seed=43, ~n=25))->toBe(false),
|
|
27
29
|
)
|
|
28
30
|
|
|
29
|
-
testSync(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
testSync(
|
|
32
|
+
"a fresh generator does not continue the previous one",
|
|
33
|
+
() => {
|
|
34
|
+
let r = Seed_Random.make(~seed=7)
|
|
35
|
+
let first = Seed_Random.float(r)
|
|
36
|
+
let _ = Seed_Random.float(r)
|
|
37
|
+
expect(Seed_Random.float(Seed_Random.make(~seed=7)))->toEqual(first)
|
|
38
|
+
},
|
|
39
|
+
)
|
|
35
40
|
})
|
|
36
41
|
|
|
37
42
|
describe("float stays a unit interval:", () => {
|
|
38
43
|
// mulberry32's last step is a signed 32-bit value folded back to unsigned.
|
|
39
44
|
// Get that fold wrong and roughly half the draws come out negative, which
|
|
40
45
|
// every consumer below (int, pick, sampleWeighted) silently mis-scales.
|
|
41
|
-
testSync(
|
|
42
|
-
|
|
46
|
+
testSync(
|
|
47
|
+
"every draw is in [0, 1)",
|
|
48
|
+
() => expect(take(~seed=99, ~n=500)->Array.every(f => f >= 0.0 && f < 1.0))->toBe(true),
|
|
43
49
|
)
|
|
44
50
|
|
|
45
|
-
testSync(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
testSync(
|
|
52
|
+
"and the draws are not all the same value",
|
|
53
|
+
() => {
|
|
54
|
+
let draws = take(~seed=99, ~n=100)
|
|
55
|
+
expect(draws->Array.some(f => f != draws->Array.getUnsafe(0)))->toBe(true)
|
|
56
|
+
},
|
|
57
|
+
)
|
|
49
58
|
})
|
|
50
59
|
|
|
51
60
|
describe("int is inclusive on both ends, as documented:", () => {
|
|
@@ -54,40 +63,50 @@ describe("Seed_Random:", () => {
|
|
|
54
63
|
Array.make(~length=n, 0)->Array.map(_ => Seed_Random.int(r, ~min, ~max))
|
|
55
64
|
}
|
|
56
65
|
|
|
57
|
-
testSync(
|
|
58
|
-
|
|
66
|
+
testSync(
|
|
67
|
+
"never escapes the range",
|
|
68
|
+
() => expect(draws(~min=3, ~max=7, ~n=500)->Array.every(i => i >= 3 && i <= 7))->toBe(true),
|
|
59
69
|
)
|
|
60
70
|
|
|
61
71
|
// The documented contract is "inclusive on both ends"; an off-by-one in the
|
|
62
72
|
// `max - min + 1` scaling shows up as a bound that is never drawn.
|
|
63
|
-
testSync(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
testSync(
|
|
74
|
+
"reaches both bounds",
|
|
75
|
+
() => {
|
|
76
|
+
let d = draws(~min=3, ~max=7, ~n=500)
|
|
77
|
+
expect((d->Array.includes(3), d->Array.includes(7)))->toEqual((true, true))
|
|
78
|
+
},
|
|
79
|
+
)
|
|
67
80
|
|
|
68
|
-
testSync(
|
|
69
|
-
|
|
81
|
+
testSync(
|
|
82
|
+
"a single-value range is that value",
|
|
83
|
+
() => expect(draws(~min=4, ~max=4, ~n=10)->Array.every(i => i == 4))->toBe(true),
|
|
70
84
|
)
|
|
71
85
|
})
|
|
72
86
|
|
|
73
87
|
describe("pick:", () => {
|
|
74
|
-
testSync(
|
|
75
|
-
|
|
88
|
+
testSync(
|
|
89
|
+
"returns None for an empty array",
|
|
90
|
+
() => expect(Seed_Random.make(~seed=1)->Seed_Random.pick([]))->toEqual(None),
|
|
76
91
|
)
|
|
77
92
|
|
|
78
|
-
testSync(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
testSync(
|
|
94
|
+
"always lands on a member",
|
|
95
|
+
() => {
|
|
96
|
+
let r = Seed_Random.make(~seed=2)
|
|
97
|
+
let xs = ["a", "b", "c", "d"]
|
|
98
|
+
let hits =
|
|
99
|
+
Array.make(~length=200, "")->Array.map(_ => r->Seed_Random.pickOr(~fallback="MISS", xs))
|
|
100
|
+
expect(hits->Array.every(h => xs->Array.includes(h)))->toBe(true)
|
|
101
|
+
},
|
|
102
|
+
)
|
|
86
103
|
|
|
87
|
-
testSync(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
104
|
+
testSync(
|
|
105
|
+
"pickOr falls back only when there is nothing to pick",
|
|
106
|
+
() =>
|
|
107
|
+
expect(Seed_Random.make(~seed=3)->Seed_Random.pickOr(~fallback="fallback", []))->toBe(
|
|
108
|
+
"fallback",
|
|
109
|
+
),
|
|
91
110
|
)
|
|
92
111
|
})
|
|
93
112
|
|
|
@@ -96,65 +115,86 @@ describe("Seed_Random:", () => {
|
|
|
96
115
|
|
|
97
116
|
// The documented shuffle use: equal weights, count = length. If sampling
|
|
98
117
|
// ever replaced instead of removing, duplicates would appear here.
|
|
99
|
-
testSync(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
118
|
+
testSync(
|
|
119
|
+
"count = length yields a permutation, not a multiset",
|
|
120
|
+
() => {
|
|
121
|
+
let xs = ["a", "b", "c", "d", "e"]
|
|
122
|
+
let out = Seed_Random.make(~seed=11)->Seed_Random.sampleWeighted(equal(xs), ~count=5)
|
|
123
|
+
let sorted = out->Array.toSorted(String.compare)
|
|
124
|
+
expect((out->Array.length, sorted))->toEqual((5, ["a", "b", "c", "d", "e"]))
|
|
125
|
+
},
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
testSync(
|
|
129
|
+
"count < length returns exactly count distinct items",
|
|
130
|
+
() => {
|
|
131
|
+
let xs = ["a", "b", "c", "d", "e"]
|
|
132
|
+
let out = Seed_Random.make(~seed=12)->Seed_Random.sampleWeighted(equal(xs), ~count=3)
|
|
133
|
+
let distinct = Set.fromArray(out)->Set.size
|
|
134
|
+
expect((out->Array.length, distinct))->toEqual((3, 3))
|
|
135
|
+
},
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
testSync(
|
|
139
|
+
"count > length clamps to the pool",
|
|
140
|
+
() =>
|
|
141
|
+
expect(
|
|
142
|
+
Seed_Random.make(~seed=13)
|
|
143
|
+
->Seed_Random.sampleWeighted(equal(["a", "b"]), ~count=99)
|
|
144
|
+
->Array.length,
|
|
145
|
+
)->toBe(2),
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
testSync(
|
|
149
|
+
"a zero-weight entry is never chosen while others remain",
|
|
150
|
+
() => {
|
|
151
|
+
let out =
|
|
152
|
+
Seed_Random.make(~seed=14)->Seed_Random.sampleWeighted(
|
|
153
|
+
[("never", 0.0), ("a", 1.0), ("b", 1.0)],
|
|
154
|
+
~count=2,
|
|
155
|
+
)
|
|
156
|
+
expect(out->Array.includes("never"))->toBe(false)
|
|
157
|
+
},
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
testSync(
|
|
161
|
+
"is reproducible for a given seed",
|
|
162
|
+
() => {
|
|
163
|
+
let run = () =>
|
|
164
|
+
Seed_Random.make(~seed=15)->Seed_Random.sampleWeighted(
|
|
165
|
+
equal(["a", "b", "c", "d", "e", "f"]),
|
|
166
|
+
~count=4,
|
|
167
|
+
)
|
|
168
|
+
expect(run())->toEqual(run())
|
|
169
|
+
},
|
|
170
|
+
)
|
|
138
171
|
})
|
|
139
172
|
|
|
140
173
|
describe("zipfWeights skews toward the head:", () => {
|
|
141
174
|
let weights = Seed_Random.zipfWeights(["a", "b", "c", "d", "e"], ~exponent=1.1)
|
|
142
175
|
|
|
143
|
-
testSync(
|
|
144
|
-
|
|
176
|
+
testSync(
|
|
177
|
+
"keeps every item, in order",
|
|
178
|
+
() => expect(weights->Array.map(((item, _)) => item))->toEqual(["a", "b", "c", "d", "e"]),
|
|
145
179
|
)
|
|
146
180
|
|
|
147
|
-
testSync(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
ws->Array.
|
|
151
|
-
|
|
152
|
-
|
|
181
|
+
testSync(
|
|
182
|
+
"weight decreases monotonically with rank",
|
|
183
|
+
() => {
|
|
184
|
+
let ws = weights->Array.map(((_, w)) => w)
|
|
185
|
+
let decreasing =
|
|
186
|
+
ws->Array.everyWithIndex((w, i) => i == 0 || w < ws->Array.getUnsafe(i - 1))
|
|
187
|
+
expect(decreasing)->toBe(true)
|
|
188
|
+
},
|
|
189
|
+
)
|
|
153
190
|
|
|
154
|
-
testSync(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
191
|
+
testSync(
|
|
192
|
+
"the leader is clear — first outweighs last several times over",
|
|
193
|
+
() => {
|
|
194
|
+
let first = weights->Array.get(0)->Option.mapOr(0.0, ((_, w)) => w)
|
|
195
|
+
let last = weights->Array.get(4)->Option.mapOr(0.0, ((_, w)) => w)
|
|
196
|
+
expect(first > last *. 3.0)->toBe(true)
|
|
197
|
+
},
|
|
198
|
+
)
|
|
159
199
|
})
|
|
160
200
|
})
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
open Seed_Types
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
let envOr = (key: string, fallback: string): string =>
|
|
7
6
|
NodeProcess.env->Dict.get(key)->Option.getOr(fallback)
|
|
8
7
|
|
|
@@ -58,14 +57,12 @@ let verifyViews = async (client: Seed_Client.t, ~views: array<view>): dict<int>
|
|
|
58
57
|
Console.log(` ${count->Int.toString->String.padStart(4, " ")} ${name}${note}`)
|
|
59
58
|
})
|
|
60
59
|
|
|
61
|
-
let empty =
|
|
62
|
-
|
|
63
|
-
->
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
)
|
|
60
|
+
let empty = views->Array.filterMap(v =>
|
|
61
|
+
switch v {
|
|
62
|
+
| Seeded(name) if counts->Dict.get(name)->Option.getOr(0) == 0 => Some(name)
|
|
63
|
+
| _ => None
|
|
64
|
+
}
|
|
65
|
+
)
|
|
69
66
|
if empty->Array.length > 0 {
|
|
70
67
|
throw(Failed(`these views are still empty after seeding: ${empty->Array.join(", ")}`))
|
|
71
68
|
}
|
|
@@ -138,9 +135,7 @@ let run = async (main: unit => promise<unit>): unit =>
|
|
|
138
135
|
Console.error("")
|
|
139
136
|
Console.error("Seeding aborted with an unexpected error:")
|
|
140
137
|
Console.error("")
|
|
141
|
-
Console.error(
|
|
142
|
-
exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("unknown"),
|
|
143
|
-
)
|
|
138
|
+
Console.error(exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("unknown"))
|
|
144
139
|
NodeProcess.exit(1)
|
|
145
140
|
}
|
|
146
141
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -64,7 +64,9 @@ let uploadAsset = async (
|
|
|
64
64
|
// GraphQL string literals: JSON-quote the argument values (JSON string escaping is a
|
|
65
65
|
// superset-safe subset of GraphQL's for these ASCII-ish inputs).
|
|
66
66
|
let q = s => JSON.stringify(JSON.Encode.string(s))
|
|
67
|
-
let query = `mutation { r: Upload_Presign(store: ${q(store)}, fileName: ${q(
|
|
67
|
+
let query = `mutation { r: Upload_Presign(store: ${q(store)}, fileName: ${q(
|
|
68
|
+
fileName,
|
|
69
|
+
)}, contentType: ${q(contentType)}) { uploadUrl storageRef } }`
|
|
68
70
|
|
|
69
71
|
let presign = try Ok(await Seed_Client.gql(client, ~query, ~label="Upload_Presign")) catch {
|
|
70
72
|
| Seed_Types.Failed(m) => Error(m)
|
|
@@ -89,7 +91,7 @@ let uploadAsset = async (
|
|
|
89
91
|
| Ok(putRes) if putRes->responseOk => Ok(storageRef)
|
|
90
92
|
| Ok(putRes) =>
|
|
91
93
|
let detail = await putRes->responseText
|
|
92
|
-
Error(`upload PUT failed with HTTP ${
|
|
94
|
+
Error(`upload PUT failed with HTTP ${putRes->responseStatus->Int.toString}: ${detail}`)
|
|
93
95
|
}
|
|
94
96
|
| _ => Error("Upload_Presign response missing uploadUrl/storageRef")
|
|
95
97
|
}
|
|
Binary file
|
|
Binary file
|
package/lib/ocaml/Seed_Users.ast
CHANGED
|
Binary file
|
package/lib/ocaml/Seed_Users.res
CHANGED
|
@@ -11,8 +11,20 @@
|
|
|
11
11
|
|
|
12
12
|
@module("yaml") external parseYaml: string => JSON.t = "parse"
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* One account: what to log in as, what to send with it, and — when the file
|
|
16
|
+
* records one — the id the platform stamps on rows this account writes.
|
|
17
|
+
*
|
|
18
|
+
* `userId` is optional because absence is normal on both platforms: a local file
|
|
19
|
+
* may omit it and let the auth adapter default it to the username, and a
|
|
20
|
+
* hand-maintained AWS file records subs the pool minted, so it can lag.
|
|
21
|
+
*/
|
|
22
|
+
type user = {
|
|
23
|
+
username: string,
|
|
24
|
+
password: string,
|
|
25
|
+
groups: array<string>,
|
|
26
|
+
userId: option<string>,
|
|
27
|
+
}
|
|
16
28
|
|
|
17
29
|
let asString = (json: JSON.t): option<string> =>
|
|
18
30
|
switch json {
|
|
@@ -32,7 +44,7 @@ let userOf = (json: JSON.t): option<user> =>
|
|
|
32
44
|
| Some(JSON.Array(items)) => items->Array.filterMap(asString)
|
|
33
45
|
| _ => []
|
|
34
46
|
}
|
|
35
|
-
Some({username, password, groups})
|
|
47
|
+
Some({username, password, groups, userId: obj->Dict.get("userId")->Option.flatMap(asString)})
|
|
36
48
|
| _ => None
|
|
37
49
|
}
|
|
38
50
|
| _ => None
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -50,6 +50,22 @@ describe("Seed_Users.parseString:", () => {
|
|
|
50
50
|
])
|
|
51
51
|
})
|
|
52
52
|
|
|
53
|
+
// The half that was dropped, and the reason owner-scoped demo rows were keyed
|
|
54
|
+
// to ids nobody on a Cognito deployment holds: the file records the id the
|
|
55
|
+
// platform stamps, and reading it only for a password threw that away.
|
|
56
|
+
//
|
|
57
|
+
// Absent stays absent rather than defaulting to the username — a local file
|
|
58
|
+
// may omit it and let the auth adapter default it, and an AWS file records a
|
|
59
|
+
// sub no default could invent. Guessing here would look like an answer.
|
|
60
|
+
testSync("keeps the userId an entry declares, and reads a missing one as absent", () => {
|
|
61
|
+
let users = Seed_Users.parseString(document)
|
|
62
|
+
expect(users->Array.map(u => u.Seed_Users.userId))->Expect.toEqual([
|
|
63
|
+
Some("local-admin"),
|
|
64
|
+
None,
|
|
65
|
+
None,
|
|
66
|
+
])
|
|
67
|
+
})
|
|
68
|
+
|
|
53
69
|
// An entry half-written by hand is the likely flaw in this file. Dropping it
|
|
54
70
|
// keeps the other accounts selectable; failing the parse would not.
|
|
55
71
|
testSync("skips an entry that names no password", () => {
|
|
@@ -82,11 +98,32 @@ describe("Seed_Users.load:", () => {
|
|
|
82
98
|
describe("Seed_Users.label:", () => {
|
|
83
99
|
testSync("names the groups an account carries", () => {
|
|
84
100
|
expect(
|
|
85
|
-
Seed_Users.label({
|
|
101
|
+
Seed_Users.label({
|
|
102
|
+
username: "admin",
|
|
103
|
+
password: "x",
|
|
104
|
+
groups: ["Admin", "Shopper"],
|
|
105
|
+
userId: None,
|
|
106
|
+
}),
|
|
86
107
|
)->Expect.toBe("admin [Admin, Shopper]")
|
|
87
108
|
})
|
|
88
109
|
|
|
89
110
|
testSync("is the bare username when the file records no groups", () => {
|
|
90
|
-
expect(
|
|
111
|
+
expect(
|
|
112
|
+
Seed_Users.label({username: "admin", password: "x", groups: [], userId: None}),
|
|
113
|
+
)->Expect.toBe("admin")
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// The menu is unchanged by the id: an operator picks an account by name and
|
|
117
|
+
// membership, and a Cognito sub in the list would be noise at the one moment
|
|
118
|
+
// they are choosing a login.
|
|
119
|
+
testSync("says nothing about the userId", () => {
|
|
120
|
+
expect(
|
|
121
|
+
Seed_Users.label({
|
|
122
|
+
username: "admin",
|
|
123
|
+
password: "x",
|
|
124
|
+
groups: ["Admin"],
|
|
125
|
+
userId: Some("4275e4a4-00c1-70e1-1cb5-363b06b992b5"),
|
|
126
|
+
}),
|
|
127
|
+
)->Expect.toBe("admin [Admin]")
|
|
91
128
|
})
|
|
92
129
|
})
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-seed",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.21",
|
|
4
4
|
"description": "Reusable harness for seeding a Reventless platform through its public GraphQL command API",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"yaml": "^2.8.3",
|
|
8
|
-
"@reventlessdev/rescript-node": "2.0.0-alpha.
|
|
8
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.11"
|
|
9
9
|
},
|
|
10
10
|
"devDependencies": {
|
|
11
11
|
"rescript": "12.3.0",
|
package/src/Seed.res
CHANGED
|
@@ -16,12 +16,18 @@ module Connect = Seed_Connect
|
|
|
16
16
|
exception Failed = Seed_Types.Failed
|
|
17
17
|
|
|
18
18
|
// A live target a data set seeds against: an authenticated client, whether uploads
|
|
19
|
-
// are skipped this run, and
|
|
20
|
-
// `Upload_Presign` mutation on the client (route B), so no upload
|
|
19
|
+
// are skipped this run, a label, and who the run is acting as. Uploads mint through
|
|
20
|
+
// the domain API's `Upload_Presign` mutation on the client (route B), so no upload
|
|
21
|
+
// endpoint is carried.
|
|
21
22
|
type connection = Seed_Connect.connection = {
|
|
22
23
|
client: Seed_Client.t,
|
|
23
24
|
uploadsSkipped: bool,
|
|
24
25
|
label: string,
|
|
26
|
+
accounts: array<Seed_Users.user>,
|
|
27
|
+
caller: Seed_Users.user,
|
|
28
|
+
callerId: option<string>,
|
|
29
|
+
login: (~username: string, ~password: string) => promise<string>,
|
|
30
|
+
roleSwitch: option<Seed_Connect.roleSwitch>,
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
// A named, seedable data set. `seed` owns everything domain-specific.
|